• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Implementing automated compliance reporting for custom hospital clinic appointments ledgers using mpdf engine

Implementing automated compliance reporting for custom hospital clinic appointments ledgers using mpdf engine

Database Schema for Appointment Ledgers

To implement automated compliance reporting for custom hospital clinic appointments, a robust database schema is paramount. This schema must capture all necessary details for auditing and reporting, including patient information, appointment specifics, and any compliance-related flags. We’ll assume a relational database like PostgreSQL or MySQL. The core tables would include:

  • patients: Stores patient demographic and identification data.
  • appointments: Records each appointment, linking to patients and clinic staff.
  • compliance_records: A dedicated table for audit trails and compliance-specific events related to appointments.

Here’s a simplified SQL schema definition:

CREATE TABLE patients (
    patient_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    date_of_birth DATE NOT NULL,
    national_id VARCHAR(50) UNIQUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE clinics (
    clinic_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    clinic_name VARCHAR(255) NOT NULL,
    location VARCHAR(255)
);

CREATE TABLE staff (
    staff_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    role VARCHAR(100) NOT NULL,
    clinic_id UUID REFERENCES clinics(clinic_id)
);

CREATE TABLE appointments (
    appointment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    patient_id UUID NOT NULL REFERENCES patients(patient_id),
    clinic_id UUID NOT NULL REFERENCES clinics(clinic_id),
    staff_id UUID REFERENCES staff(staff_id),
    appointment_time TIMESTAMP WITH TIME ZONE NOT NULL,
    duration_minutes INT,
    status VARCHAR(50) NOT NULL DEFAULT 'scheduled', -- e.g., scheduled, completed, cancelled, no-show
    notes TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE compliance_records (
    compliance_record_id BIGSERIAL PRIMARY KEY,
    appointment_id UUID REFERENCES appointments(appointment_id),
    patient_id UUID REFERENCES patients(patient_id),
    event_type VARCHAR(100) NOT NULL, -- e.g., 'appointment_created', 'appointment_modified', 'appointment_cancelled', 'consent_obtained'
    event_timestamp TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    user_id VARCHAR(255), -- User performing the action
    details JSONB -- Additional context, e.g., old/new values for modifications
);

-- Indexes for performance
CREATE INDEX idx_appointments_patient_id ON appointments(patient_id);
CREATE INDEX idx_appointments_clinic_id ON appointments(clinic_id);
CREATE INDEX idx_appointments_appointment_time ON appointments(appointment_time);
CREATE INDEX idx_compliance_records_appointment_id ON compliance_records(appointment_id);
CREATE INDEX idx_compliance_records_event_timestamp ON compliance_records(event_timestamp);

Integrating mPDF for PDF Generation

The mPDF library is a powerful PHP library for generating PDF documents from HTML and CSS. It’s well-suited for creating structured reports that need to adhere to specific formatting requirements for compliance.

First, ensure you have mPDF installed. The recommended way is via Composer:

composer require mpdf/mpdf

Next, we’ll create a PHP class to handle the PDF generation. This class will query the database for appointment data and then use mPDF to render it into a PDF report.

<?php
require_once __DIR__ . '/vendor/autoload.php'; // Adjust path as necessary

use Mpdf\Mpdf;
use Mpdf\MpdfException;

class AppointmentComplianceReporter {
    private $db; // Database connection object (e.g., PDO)

    public function __construct(PDO $db) {
        $this->db = $db;
    }

    /**
     * Fetches appointment data for a given date range.
     * @param string $startDate
     * @param string $endDate
     * @return array
     */
    private function getAppointmentsForDateRange(string $startDate, string $endDate): array {
        $sql = "
            SELECT
                a.appointment_id,
                a.appointment_time,
                a.status,
                a.duration_minutes,
                p.first_name AS patient_first_name,
                p.last_name AS patient_last_name,
                p.date_of_birth AS patient_dob,
                p.national_id AS patient_national_id,
                c.clinic_name,
                s.first_name AS staff_first_name,
                s.last_name AS staff_last_name,
                s.role AS staff_role
            FROM appointments a
            JOIN patients p ON a.patient_id = p.patient_id
            JOIN clinics c ON a.clinic_id = c.clinic_id
            LEFT JOIN staff s ON a.staff_id = s.staff_id
            WHERE a.appointment_time BETWEEN :start_date AND :end_date
            ORDER BY a.appointment_time ASC
        ";

        try {
            $stmt = $this->db->prepare($sql);
            $stmt->bindParam(':start_date', $startDate);
            $stmt->bindParam(':end_date', $endDate);
            $stmt->execute();
            return $stmt->fetchAll(PDO::FETCH_ASSOC);
        } catch (PDOException $e) {
            error_log("Database error fetching appointments: " . $e->getMessage());
            return [];
        }
    }

    /**
     * Generates the PDF report.
     * @param string $startDate
     * @param string $endDate
     * @param string $outputFilePath
     * @return bool True on success, false on failure.
     */
    public function generateReport(string $startDate, string $endDate, string $outputFilePath): bool {
        $appointments = $this->getAppointmentsForDateRange($startDate, $endDate);

        if (empty($appointments)) {
            error_log("No appointments found for the specified date range.");
            return false;
        }

        $mpdf = new Mpdf([
            'mode' => 'utf-8',
            'format' => 'A4-P', // Portrait
            'margin_left' => 15,
            'margin_right' => 15,
            'margin_top' => 20,
            'margin_bottom' => 20,
            'margin_header' => 10,
            'margin_footer' => 10,
            'default_font_size' => 10,
            'default_font' => 'dejavusans' // Ensure font supports special characters
        ]);

        // Set header and footer
        $header = '<div style="text-align: right; font-weight: bold;">Hospital Appointment Ledger Report</div>';
        $footer = '<div style="text-align: center;">Page {PAGENO} of {nbpg}</div>';
        $mpdf->SetHTMLHeader($header);
        $mpdf->SetHTMLFooter($footer);

        // Build HTML content
        $html = '<h1>Appointment Ledger Report</h1>';
        $html .= '<p>Report Period: ' . date('Y-m-d', strtotime($startDate)) . ' to ' . date('Y-m-d', strtotime($endDate)) . '</p>';

        $html .= '<table border="1" cellpadding="5" cellspacing="0" style="width: 100%; border-collapse: collapse;">';
        $html .= '<thead><tr style="background-color: #f2f2f2;">
                    <th>Date & Time</th>
                    <th>Patient Name</th>
                    <th>Patient ID</th>
                    <th>Clinic</th>
                    <th>Provider</th>
                    <th>Status</th>
                    <th>Duration (min)</th>
                  </tr></thead><tbody>';

        foreach ($appointments as $appointment) {
            $appointmentDateTime = new DateTime($appointment['appointment_time']);
            $html .= '<tr>
                        <td>' . $appointmentDateTime->format('Y-m-d H:i') . '</td>
                        <td>' . htmlspecialchars($appointment['patient_last_name'] . ', ' . $appointment['patient_first_name']) . '</td>
                        <td>' . htmlspecialchars($appointment['patient_national_id'] ?? 'N/A') . '</td>
                        <td>' . htmlspecialchars($appointment['clinic_name']) . '</td>
                        <td>' . htmlspecialchars($appointment['staff_last_name'] . ', ' . $appointment['staff_first_name'] . ' (' . $appointment['staff_role'] . ')') . '</td>
                        <td>' . htmlspecialchars($appointment['status']) . '</td>
                        <td>' . $appointment['duration_minutes'] . '</td>
                      </tr>';
        }

        $html .= '</tbody></table>';

        try {
            $mpdf->WriteHTML($html);
            $mpdf->Output($outputFilePath, \Mpdf\Output\Destination::FILE);
            return true;
        } catch (MpdfException $e) {
            error_log("mPDF generation error: " . $e->getMessage());
            return false;
        }
    }
}

// Example Usage (assuming $pdo is your PDO database connection)
/*
$dbHost = 'localhost';
$dbName = 'hospital_db';
$dbUser = 'user';
$dbPass = 'password';

try {
    $pdo = new PDO("pgsql:host=$dbHost;dbname=$dbName", $dbUser, $dbPass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    $reporter = new AppointmentComplianceReporter($pdo);
    $startDate = '2023-10-01';
    $endDate = '2023-10-31';
    $outputFile = '/path/to/save/report_' . date('Ymd') . '.pdf';

    if ($reporter->generateReport($startDate, $endDate, $outputFile)) {
        echo "Report generated successfully at: " . $outputFile;
    } else {
        echo "Failed to generate report.";
    }

} catch (PDOException $e) {
    die("Database connection failed: " . $e->getMessage());
}
*/
?>

Automating Report Generation with Cron Jobs

To ensure regular and automated compliance reporting, we can leverage cron jobs. A cron job is a time-based job scheduler in Unix-like operating systems. We’ll create a simple PHP script that can be executed by cron.

First, let’s create a script, say generate_daily_report.php:

<?php
// generate_daily_report.php

require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/src/AppointmentComplianceReporter.php'; // Assuming the class is in src/

// --- Database Configuration ---
$dbHost = getenv('DB_HOST') ?: 'localhost';
$dbName = getenv('DB_NAME') ?: 'hospital_db';
$dbUser = getenv('DB_USER') ?: 'user';
$dbPass = getenv('DB_PASS') ?: 'password';
$dbPort = getenv('DB_PORT') ?: '5432'; // Default for PostgreSQL
$dbType = getenv('DB_TYPE') ?: 'pgsql'; // 'pgsql' or 'mysql'

// --- Report Configuration ---
$reportOutputDir = getenv('REPORT_OUTPUT_DIR') ?: '/var/www/reports'; // Ensure this directory is writable by the cron user
$reportDate = getenv('REPORT_DATE') ?: date('Y-m-d'); // Default to today's date

// --- Date Range Calculation ---
// For daily reports, we'll report on the previous day's appointments
$reportEndDate = $reportDate . ' 23:59:59';
$reportStartDate = date('Y-m-d 00:00:00', strtotime($reportDate . ' -1 day'));

// --- Ensure output directory exists ---
if (!is_dir($reportOutputDir)) {
    if (!mkdir($reportOutputDir, 0755, true)) {
        die("Error: Could not create report output directory: {$reportOutputDir}\n");
    }
}

// --- Database Connection ---
try {
    $dsn = "{$dbType}:host={$dbHost};port={$dbPort};dbname={$dbName}";
    $pdo = new PDO($dsn, $dbUser, $dbPass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e) {
    error_log("Database connection failed: " . $e->getMessage());
    die("Database connection failed. Check logs.\n");
}

// --- Generate Report ---
$reporter = new AppointmentComplianceReporter($pdo);
$outputFileName = "appointments_ledger_{$reportDate}.pdf";
$outputFilePath = rtrim($reportOutputDir, '/') . '/' . $outputFileName;

echo "Attempting to generate report for {$reportStartDate} to {$reportEndDate}...\n";

if ($reporter->generateReport($reportStartDate, $reportEndDate, $outputFilePath)) {
    echo "Report generated successfully: {$outputFilePath}\n";
    // Optional: Log success to a file or monitoring system
} else {
    echo "Failed to generate report.\n";
    // Optional: Log failure, send alert
    exit(1); // Exit with a non-zero status to indicate failure to cron
}
?>

Now, set up a cron job to run this script daily. Open your crontab for editing:

crontab -e

Add a line like this to run the script every day at 2:00 AM:

# Run daily appointment compliance report at 2:00 AM
0 2 * * * /usr/bin/php /path/to/your/project/generate_daily_report.php >> /var/log/appointment_report.log 2>&1

Explanation:

  • 0 2 * * *: This is the cron schedule. It means at minute 0, hour 2, every day of the month, every month, every day of the week.
  • /usr/bin/php: The path to your PHP executable. Verify this with which php.
  • /path/to/your/project/generate_daily_report.php: The absolute path to your report generation script.
  • >> /var/log/appointment_report.log 2>&1: This redirects both standard output and standard error to a log file, which is crucial for debugging cron job failures. Ensure the cron user has write permissions to this log file and directory.

Important Considerations for Cron:

  • Environment Variables: Cron jobs run with a minimal environment. If your script relies on environment variables (like database credentials), you might need to define them within the crontab itself or source a configuration file. Using `getenv()` as shown in the script is a good practice for flexibility.
  • File Permissions: The user running the cron job (often `www-data` or a dedicated system user) must have read access to the script and write access to the output directory and log file.
  • Paths: Always use absolute paths for executables and script files within cron jobs.
  • Error Handling: The redirection `2>&1` is vital. Regularly check the log file for any errors.

Enhancing Security and Compliance Auditing

Beyond basic reporting, true compliance requires a detailed audit trail. The compliance_records table is designed for this. Every significant action related to an appointment (creation, modification, cancellation, patient check-in/out, consent updates) should trigger an entry in this table.

Example: Logging Appointment Creation

// Inside your appointment management logic, after a successful save:

public function createAppointment(array $data, string $userId): ?string {
    // ... (validation and database insertion logic) ...

    $appointmentId = $this->saveAppointmentToDb($data); // Assume this returns the new appointment_id

    if ($appointmentId) {
        // Log the creation event
        $this->logComplianceEvent(
            $appointmentId,
            $data['patient_id'],
            'appointment_created',
            $userId,
            ['appointment_details' => $data] // Store relevant initial data
        );
        return $appointmentId;
    }
    return null;
}

// Method to log compliance events
private function logComplianceEvent(string $appointmentId, string $patientId, string $eventType, string $userId, array $details = []): void {
    $sql = "
        INSERT INTO compliance_records (appointment_id, patient_id, event_type, user_id, details)
        VALUES (:appointment_id, :patient_id, :event_type, :user_id, :details::jsonb)
    ";
    try {
        $stmt = $this->db->prepare($sql);
        $stmt->bindParam(':appointment_id', $appointmentId);
        $stmt->bindParam(':patient_id', $patientId);
        $stmt->bindParam(':event_type', $eventType);
        $stmt->bindParam(':user_id', $userId);
        $stmt->bindValue(':details', json_encode($details));
        $stmt->execute();
    } catch (PDOException $e) {
        error_log("Failed to log compliance event ({$eventType}) for appointment {$appointmentId}: " . $e->getMessage());
        // Depending on criticality, you might want to throw an exception or trigger an alert here.
    }
}

Similarly, when an appointment is modified or cancelled, log these events with details about the changes. For modifications, the details JSONB field can store a diff of the old and new values, providing a clear audit trail of what changed, when, and by whom.

The automated PDF reports generated by mPDF serve as a periodic summary for compliance officers, while the compliance_records table provides the granular, immutable audit log necessary for deep investigations and regulatory checks.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Leveraging PHP 8’s JIT Compiler and Vector APIs for Extreme Web Application Performance
  • Leveraging PHP 8 JIT and AWS Lambda for High-Performance, Serverless WordPress REST API Backends
  • Beyond the Basics: Leveraging PHP 8.3’s JIT Compiler and Fibers for High-Concurrency Laravel Applications
  • Zero-Downtime Deployments with Docker, Laravel, and AWS ECS: A Deep Dive into Blue/Green Strategies
  • Leveraging PHP 9’s JIT and Concurrency Features for High-Performance Laravel Microservices on AWS ECS

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (11)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (6)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (15)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (18)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (24)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Leveraging PHP 8's JIT Compiler and Vector APIs for Extreme Web Application Performance
  • Leveraging PHP 8 JIT and AWS Lambda for High-Performance, Serverless WordPress REST API Backends
  • Beyond the Basics: Leveraging PHP 8.3's JIT Compiler and Fibers for High-Concurrency Laravel Applications

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Security & Compliance (650)
  • SEO & Growth (492)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala