
# GHS Nurse Appraisal System — README

**Version:** 1.0  
**Stack:** Core PHP (no framework), MySQL, TCPDF/FPDF for PDF export  
**Author:** Generated helper for Samuel  
**Goal:** Guide to build a multi-step nurse appraisal system where nurses log in (by Phone or Staff ID), submit/edit annual appraisals, and download a PDF matching the original Ghana Health Service appraisal form.

---

## Overview

This README will guide you step-by-step to build the appraisal system:

- Admin adds nurse accounts (system generates a random password and sends it via SMS).
- Nurses log in using **Staff ID or Phone + Password**.
- Nurses fill a **multistep appraisal form** (sections A–F), which is saved to MySQL.
- Nurses can edit submitted appraisals and download an official-looking **PDF** of the appraisal.
- One nurse can have **multiple appraisals** stored over time.

---

## Prerequisites

- PHP 7.4 / 8.x (recommended) with these extensions enabled:
  - `pdo_mysql`, `openssl`, `mbstring`, `gd` (for TCPDF), and `session`
- MySQL 5.7+ or MariaDB
- A web server (XAMPP, LAMP, WAMP, etc.)
- Composer (optional, if you want to manage libraries)
- TCPDF or FPDF library for PDF generation (instructions below)
- An SMS provider account (Twilio, Africa's Talking, or any provider with an API)

---

## File structure (suggested)

```
/appraisal/
  ├─ public/
  │   ├─ index.php           (redirect to login or dashboard)
  │   ├─ login.php
  │   ├─ logout.php
  │   ├─ dashboard.php
  │   ├─ appraisal_form.php  (multistep form)
  │   ├─ edit_appraisal.php
  │   ├─ download_pdf.php
  │   └─ assets/ (css, js)
  ├─ src/
  │   ├─ db.php
  │   ├─ auth.php
  │   ├─ sms.php             (SMS helper)
  │   ├─ pdf_helper.php
  │   └─ controllers/
  ├─ sql/
  │   └─ schema.sql
  ├─ vendor/ (optional composer)
  └─ README_Appraisal_System.md
```

---

## Database Setup

1. Create a database (example `nurse_appraisal_db`).
2. Run the SQL script in `sql/schema.sql` (below is the full schema). This creates all required tables.

**SQL schema (save as `sql/schema.sql`)**

```sql
CREATE DATABASE IF NOT EXISTS nurse_appraisal_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE nurse_appraisal_db;

-- users table (nurses)
CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  staff_id VARCHAR(50) UNIQUE,
  phone VARCHAR(20) UNIQUE,
  full_name VARCHAR(150),
  dob DATE,
  gender ENUM('Male','Female','Other') DEFAULT 'Other',
  password VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- appraisals table
CREATE TABLE appraisals (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  period_from DATE,
  period_to DATE,
  date_set DATE,
  job_title VARCHAR(100),
  grade VARCHAR(100),
  profession VARCHAR(100),
  specialty VARCHAR(100),
  facility VARCHAR(200),
  district VARCHAR(150),
  department VARCHAR(150),
  appraiser_name VARCHAR(150),
  appraiser_grade VARCHAR(100),
  finalized TINYINT(1) DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NULL DEFAULT NULL,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- trainings table (multiple entries per appraisal)
CREATE TABLE trainings (
  id INT AUTO_INCREMENT PRIMARY KEY,
  appraisal_id INT NOT NULL,
  title VARCHAR(255),
  institution VARCHAR(255),
  training_date DATE,
  FOREIGN KEY (appraisal_id) REFERENCES appraisals(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- objectives table (key results areas & objectives)
CREATE TABLE objectives (
  id INT AUTO_INCREMENT PRIMARY KEY,
  appraisal_id INT NOT NULL,
  key_area VARCHAR(255),
  objective TEXT,
  activities TEXT,
  resources TEXT,
  review_progress TEXT,
  remarks TEXT,
  rating INT,
  FOREIGN KEY (appraisal_id) REFERENCES appraisals(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- competencies table (factors and ratings)
CREATE TABLE competencies (
  id INT AUTO_INCREMENT PRIMARY KEY,
  appraisal_id INT NOT NULL,
  factor VARCHAR(150),
  rating TINYINT,
  FOREIGN KEY (appraisal_id) REFERENCES appraisals(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- comments table
CREATE TABLE comments (
  id INT AUTO_INCREMENT PRIMARY KEY,
  appraisal_id INT NOT NULL,
  strengths TEXT,
  improvements TEXT,
  training_recommendations TEXT,
  appraiser_comments TEXT,
  appraisee_comments TEXT,
  countersigning_officer_comments TEXT,
  FOREIGN KEY (appraisal_id) REFERENCES appraisals(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

---

## Configuration (`src/db.php`)

Create a simple PDO wrapper for DB connection. Save as `src/db.php`:

```php
<?php
// src/db.php
$DB_HOST = '127.0.0.1';
$DB_NAME = 'nurse_appraisal_db';
$DB_USER = 'root';
$DB_PASS = '';

try {
    $pdo = new PDO("mysql:host={$DB_HOST};dbname={$DB_NAME};charset=utf8mb4", $DB_USER, $DB_PASS, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]);
} catch (PDOException $e) {
    die("DB Connection failed: " . $e->getMessage());
}
```

---

## Admin: Add Nurse (register_user.php)

This script is used by an Admin to create nurse accounts. It generates a **random password**, hashes it, saves user record, and calls `send_sms()` placeholder.

```php
<?php
// public/register_user.php
require_once __DIR__ . '/../src/db.php';
require_once __DIR__ . '/../src/sms.php';

function generateRandomPassword($len = 8) {
    return bin2hex(random_bytes($len/2)); // even number length
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $staff_id = $_POST['staff_id'] ?? null;
    $phone = $_POST['phone'] ?? null;
    $full_name = $_POST['full_name'] ?? null;

    if (!$staff_id || !$phone || !$full_name) {
        die('Missing fields');
    }

    $rawPassword = generateRandomPassword(8);
    $hashed = password_hash($rawPassword, PASSWORD_DEFAULT);

    $stmt = $pdo->prepare("INSERT INTO users (staff_id, phone, full_name, password) VALUES (?, ?, ?, ?)");
    $stmt->execute([$staff_id, $phone, $full_name, $hashed]);
    $userId = $pdo->lastInsertId();

    // send SMS - implement send_sms in src/sms.php
    $message = "Your appraisal account was created. Login: {$staff_id} or {$phone}. Password: {$rawPassword}";
    send_sms($phone, $message);

    echo "User added (ID: $userId). Password sent by SMS (placeholder).";
}
```

**Note:** `send_sms()` is a placeholder. Implement it using your provider (Twilio, Africa's Talking). Keep SMS messages secure — avoid sending plain passwords in production; consider asking user to reset password on first login.

---

## SMS helper (`src/sms.php`)

Create a placeholder. Replace with real API code.

```php
<?php
// src/sms.php
function send_sms($to, $message) {
    // TODO: Replace this with your SMS provider API integration.
    // Example: use Twilio REST API via cURL or PHP SDK.
    // For now we log to a file for testing:
    $log = "[".date('Y-m-d H:i:s')."] SMS to {$to}: {$message}\n";
    file_put_contents(__DIR__ . '/../sms_log.txt', $log, FILE_APPEND);
    return true;
}
```

---

## Login (`public/login.php`)

Allow login with staff_id **or** phone.

```php
<?php
// public/login.php
session_start();
require_once __DIR__ . '/../src/db.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $identifier = trim($_POST['identifier']); // staff_id or phone
    $password = $_POST['password'];

    $stmt = $pdo->prepare("SELECT * FROM users WHERE staff_id = ? OR phone = ?");
    $stmt->execute([$identifier, $identifier]);
    $user = $stmt->fetch();

    if ($user && password_verify($password, $user['password'])) {
        session_regenerate_id(true);
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['full_name'] = $user['full_name'];
        header("Location: dashboard.php");
        exit;
    } else {
        $error = "Invalid credentials";
    }
}
?>
<!-- HTML form (simple) -->
<form method="post">
  <input name="identifier" placeholder="Staff ID or Phone" required />
  <input type="password" name="password" placeholder="Password" required />
  <button type="submit">Login</button>
</form>
```

---

## Multistep Form Strategy (`public/appraisal_form.php`)

Two recommended approaches:
1. **Create appraisal row on Step 1**: Insert minimal appraisal record (user_id, period_from, period_to). Store `appraisal_id` in `$_SESSION` and update other tables as steps are completed.
2. **Keep session data until final step**: Store each step in session and create DB entries only when user clicks Finalize.

**Recommended (Option 1)**: create the appraisal record first — this enables users to save/continue from different devices.

**Basic flow (example):**
- Step 1 (Personal / Section A): create `appraisals` row → `INSERT`, store `appraisal_id` in session.
- Step 2 (Trainings): add rows to `trainings` table with `appraisal_id`.
- Step 3 (Objectives): loop through objectives and `INSERT` to `objectives`.
- Step 4 (Competencies): insert `competencies`.
- Step 5 (Comments & finalize): insert to `comments` and set `finalized = 1` if ready.

**Example: create appraisal (Step 1)**

```php
// public/appraisal_step1.php
session_start();
require_once __DIR__ . '/../src/db.php';

if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit; }

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $period_from = $_POST['period_from'];
    $period_to = $_POST['period_to'];
    $date_set = $_POST['date_set'];
    $job_title = $_POST['job_title'];

    $stmt = $pdo->prepare("INSERT INTO appraisals (user_id, period_from, period_to, date_set, job_title) VALUES (?, ?, ?, ?, ?)");
    $stmt->execute([$_SESSION['user_id'], $period_from, $period_to, $date_set, $job_title]);
    $appraisal_id = $pdo->lastInsertId();
    $_SESSION['appraisal_id'] = $appraisal_id;

    header("Location: appraisal_step2.php");
    exit;
}
```

**Example: add training rows (Step 2)**

```php
// public/appraisal_step2.php
session_start();
require_once __DIR__ . '/../src/db.php';
$appraisal_id = $_SESSION['appraisal_id'];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // assuming training fields as arrays training_title[], training_institution[], training_date[]
    foreach ($_POST['training_title'] as $i => $title) {
        $inst = $_POST['training_institution'][$i] ?? null;
        $tdate = $_POST['training_date'][$i] ?? null;
        if ($title) {
            $stmt = $pdo->prepare("INSERT INTO trainings (appraisal_id, title, institution, training_date) VALUES (?, ?, ?, ?)");
            $stmt->execute([$appraisal_id, $title, $inst, $tdate]);
        }
    }
    header("Location: appraisal_step3.php");
}
```

---

## Editing an Appraisal

- `edit_appraisal.php` should validate that `$_SESSION['user_id']` owns the `appraisal_id`.
- Load rows from `appraisals`, `trainings`, `objectives`, `competencies`, `comments` and display in the form.
- Save via `UPDATE` / `DELETE` + re-INSERT for child tables as needed.

**Security tip:** Ensure only owner (or admin) can edit. Use checks like:

```php
$stmt = $pdo->prepare("SELECT user_id FROM appraisals WHERE id = ?");
$stmt->execute([$appraisal_id]);
$owner = $stmt->fetchColumn();
if ($owner != $_SESSION['user_id']) {
    die('Unauthorized');
}
```

---

## PDF Generation (download_pdf.php)

- Use **TCPDF** for more control on layout (recommended) or **FPDF** for simpler output.
- Install TCPDF by downloading and dropping the library folder into your project (or use composer).

**Example with TCPDF (skeleton):**

```php
<?php
// public/download_pdf.php
require_once __DIR__ . '/../src/db.php';
require_once __DIR__ . '/../vendor/tcpdf_min/tcpdf.php'; // path to TCPDF

session_start();
if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit; }

$appraisal_id = $_GET['id'] ?? null;

// validate ownership
$stmt = $pdo->prepare("SELECT user_id FROM appraisals WHERE id = ?");
$stmt->execute([$appraisal_id]);
if ($stmt->fetchColumn() != $_SESSION['user_id']) die('Forbidden');

// fetch data: appraisals, trainings, objectives, competencies, comments
// build HTML that mimics the original form
$html = '<h1>Ghana Health Service - Staff Performance Management Form</h1>';
// ... build an HTML table and fields that mirror the original layout ...

// create pdf
$pdf = new TCPDF(PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
$pdf->SetCreator('Appraisal System');
$pdf->SetAuthor($_SESSION['full_name']);
$pdf->AddPage();
$pdf->writeHTML($html);
$pdf->Output("appraisal_{$appraisal_id}.pdf", 'D'); // D = force download
exit;
```

**Layout tips**:
- Use `<table>` with borders and fixed widths to mimic the paper form.
- Use small font sizes where necessary and carefully align labels.
- If the official form includes logos, ensure you have permission to use them.

---

## Security Best Practices

- Always use `password_hash()` and `password_verify()`.
- Use PDO prepared statements to avoid SQL injection.
- `session_regenerate_id(true)` after login.
- Validate/escape output when rendering user input (`htmlspecialchars()`).
- Restrict editing to owners/admins only.
- Use HTTPS in production.
- In production, avoid sending raw passwords over SMS. Use a password-reset flow or send a temporary one-time code.

---

## Useful Tips & Troubleshooting

- If `PDOException` shows `access denied`, check DB credentials and user grants.
- To debug SQL queries, log them temporarily (avoid in production).
- If TCPDF complains about missing GD or fonts, ensure PHP GD extension is installed.
- File permissions: ensure your web user can write to directories used for logs or temporary PDF generation.
- If session keeps expiring, check `session.save_path` permissions and PHP session settings.

---

## Deployment Checklist

- [ ] Configure HTTPS (Let's Encrypt if needed)
- [ ] Secure DB user permissions (least privilege)
- [ ] Enable PHP error logging (but hide errors from users)
- [ ] Set file permissions correctly
- [ ] Configure an SMS provider for `send_sms()`
- [ ] Install TCPDF/FPDF and test PDF layout
- [ ] Create backup plan for database

---

## Next Steps — Features to add later

- Admin panel for user & appraisal management
- Audit trail (store changes and timestamps)
- Role-based permissions (admins, supervisors, nurses)
- Email notifications + SMS verification flows
- Dashboard analytics (counts of submitted appraisals, averages)
- Export to Excel/CSV

---

## Final Notes

This README provides a clear plan and starter code snippets to implement the appraisal system in Core PHP + MySQL. Use the SQL schema to create your database, implement the `db.php` connection, and scaffold the PHP files listed in the file structure. Start with login and user creation (so you can create accounts), then implement the multistep form and finally the PDF generation.

If you'd like, I can:
- generate the full scaffolded starter PHP files for you,
- create the `sql/schema.sql` file separately,
- or generate a working `download_pdf.php` that replicates the exact official form layout (requires iterative layout tuning).

