Automated Expense Report Generator and Approval Workflow System,PHP

👤 Sharing: AI
```php
<?php

/**
 * Automated Expense Report Generator and Approval Workflow System
 */

// ------------------------------------------------------------------
// Configuration (Database connection, user roles, etc.)
// ------------------------------------------------------------------

// Database connection details
$db_host = "localhost";
$db_name = "expense_reports";
$db_user = "root";
$db_pass = "";

// User roles (e.g., employee, manager, admin)
$roles = [
    "employee" => 1,
    "manager" => 2,
    "admin" => 3
];

// Approval levels required (e.g., Manager -> Finance)
$approval_levels = [
    $roles["manager"], // Manager approves
    $roles["admin"]   // Admin approves
];


// ------------------------------------------------------------------
// Database Connection (using PDO)
// ------------------------------------------------------------------
try {
    $pdo = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_pass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Enable error reporting
} catch (PDOException $e) {
    die("Database connection failed: " . $e->getMessage());
}



// ------------------------------------------------------------------
// Helper Functions
// ------------------------------------------------------------------

/**
 * Sanitize user input to prevent SQL injection and XSS.
 *
 * @param string $input The input string to sanitize.
 * @return string The sanitized string.
 */
function sanitizeInput(string $input): string {
    // Remove HTML tags (use with caution, consider strip_tags())
    $input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');

    // Trim whitespace
    $input = trim($input);

    return $input;
}


/**
 * Get the current user's role from the session (or wherever you store it).
 *  *For a real system, you'd implement authentication first.*
 *
 *  @return int The user's role ID, or null if not logged in.
 */
function getCurrentUserRole(): ?int {
    // In a real application, this would come from your authentication system
    // (e.g., $_SESSION['user_role_id'])
    // For this example, we'll simulate it:
    // This is for simple testing!  Remove/replace in a real app!
    if(isset($_SESSION['user_role'])) {
        return $_SESSION['user_role'];
    } else {
        return null;  // Not logged in.
    }
}


/**
 * Get the current user's ID from the session (or wherever you store it).
 * *For a real system, you'd implement authentication first.*
 *
 * @return int|null The user's ID, or null if not logged in.
 */
function getCurrentUserId(): ?int {
    // In a real application, this would come from your authentication system
    // (e.g., $_SESSION['user_id'])
    // For this example, we'll simulate it:
    // This is for simple testing! Remove/replace in a real app!
    if(isset($_SESSION['user_id'])) {
        return $_SESSION['user_id'];
    } else {
        return null; // Not logged in
    }
}


/**
 * Checks if a user has a specific role.
 *
 * @param int $requiredRole The required role ID.
 * @return bool True if the user has the role, false otherwise.
 */
function hasRole(int $requiredRole): bool {
    $userRole = getCurrentUserRole();
    return ($userRole !== null && $userRole >= $requiredRole); // Allow higher roles to also perform actions.
}



// ------------------------------------------------------------------
// Expense Report Class
// ------------------------------------------------------------------

class ExpenseReport {
    private int $id;
    private int $userId;
    private string $date;
    private string $description;
    private float $amount;
    private string $status;  // e.g., "draft", "submitted", "approved", "rejected"
    private array $approvalHistory;  // Store who approved and when


    public function __construct(int $id, int $userId, string $date, string $description, float $amount, string $status = "draft", array $approvalHistory = []) {
        $this->id = $id;
        $this->userId = $userId;
        $this->date = $date;
        $this->description = $description;
        $this->amount = $amount;
        $this->status = $status;
        $this->approvalHistory = $approvalHistory;
    }

    public function getId(): int {
        return $this->id;
    }

    public function getUserId(): int {
        return $this->userId;
    }

    public function getDate(): string {
        return $this->date;
    }

    public function getDescription(): string {
        return $this->description;
    }

    public function getAmount(): float {
        return $this->amount;
    }

    public function getStatus(): string {
        return $this->status;
    }

    public function getApprovalHistory(): array {
        return $this->approvalHistory;
    }

    public function setStatus(string $status): void {
        $this->status = $status;
    }

    public function addApproval(int $userId, string $timestamp): void {
        $this->approvalHistory[] = ["user_id" => $userId, "timestamp" => $timestamp];
    }

     /**
     * Saves the expense report to the database (either creates a new one or updates an existing one).
     *
     * @param PDO $pdo The PDO database connection object.
     * @return bool True on success, false on failure.
     */
    public function save(PDO $pdo): bool {
        if ($this->id === 0) { // New expense report
            $stmt = $pdo->prepare("INSERT INTO expense_reports (user_id, date, description, amount, status, approval_history) VALUES (?, ?, ?, ?, ?, ?)");
            $approvalHistoryJson = json_encode($this->approvalHistory); // Convert array to JSON string for storage
            $result = $stmt->execute([$this->userId, $this->date, $this->description, $this->amount, $this->status, $approvalHistoryJson]);
            if ($result) {
                $this->id = (int)$pdo->lastInsertId(); // Get the newly inserted ID.  Cast to int for type safety.
            }
            return $result; // Return true on success, false on failure.

        } else { // Existing expense report (update)
            $stmt = $pdo->prepare("UPDATE expense_reports SET user_id = ?, date = ?, description = ?, amount = ?, status = ?, approval_history = ? WHERE id = ?");
            $approvalHistoryJson = json_encode($this->approvalHistory);
            $result = $stmt->execute([$this->userId, $this->date, $this->description, $this->amount, $this->status, $approvalHistoryJson, $this->id]);
            return $result; // True on success, false on failure.
        }
    }

   /**
     * Deletes the expense report from the database.
     *
     * @param PDO $pdo The PDO database connection object.
     * @return bool True on success, false on failure.
     */
    public function delete(PDO $pdo): bool {
        $stmt = $pdo->prepare("DELETE FROM expense_reports WHERE id = ?");
        return $stmt->execute([$this->id]);
    }


    public static function getById(PDO $pdo, int $id): ?ExpenseReport {
        $stmt = $pdo->prepare("SELECT * FROM expense_reports WHERE id = ?");
        $stmt->execute([$id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($row) {
            $approvalHistory = json_decode($row['approval_history'], true); // Decode JSON string to array.
            return new ExpenseReport(
                (int)$row['id'], // Important: Cast to integer
                (int)$row['user_id'], // Important: Cast to integer
                $row['date'],
                $row['description'],
                (float)$row['amount'], // Important: Cast to float
                $row['status'],
                $approvalHistory
            );
        } else {
            return null; // Report not found
        }
    }


    public static function getAllByUserId(PDO $pdo, int $userId): array {
        $stmt = $pdo->prepare("SELECT * FROM expense_reports WHERE user_id = ?");
        $stmt->execute([$userId]);
        $reports = [];

        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $approvalHistory = json_decode($row['approval_history'], true);
            $reports[] = new ExpenseReport(
                (int)$row['id'],  // Important: Cast to integer
                (int)$row['user_id'], // Important: Cast to integer
                $row['date'],
                $row['description'],
                (float)$row['amount'], // Important: Cast to float
                $row['status'],
                $approvalHistory
            );
        }

        return $reports;
    }


     /**
     * Get all expense reports that need approval from a specific role.
     * This assumes the 'approval_history' field stores the roles that have already approved the report.
     *
     * @param PDO $pdo The PDO database connection object.
     * @param int $roleId The role ID that needs to approve the reports.
     * @return array An array of ExpenseReport objects that need approval from the given role.
     */
    public static function getPendingApprovalByRole(PDO $pdo, int $roleId): array {
        $reports = [];

        // Modified SQL query to check if the role has *not* yet approved the report.
        // We check if the role ID exists in the approval_history JSON array.  If it doesn't,
        // then the role hasn't approved the report yet.
        $stmt = $pdo->prepare("SELECT * FROM expense_reports WHERE status = 'submitted' AND NOT JSON_CONTAINS(approval_history, CAST(? AS JSON), '$[*].user_id')");
        $roleIdString = (string)$roleId; // Convert to string for JSON_CONTAINS.
        $stmt->execute([$roleIdString]);  // Bind the role ID as a string

        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $approvalHistory = json_decode($row['approval_history'], true);
            $reports[] = new ExpenseReport(
                (int)$row['id'],
                (int)$row['user_id'],
                $row['date'],
                $row['description'],
                (float)$row['amount'],
                $row['status'],
                $approvalHistory
            );
        }

        return $reports;
    }



} // End of ExpenseReport class




// ------------------------------------------------------------------
// User Interface (Very Basic - Replace with HTML/CSS/JavaScript)
// ------------------------------------------------------------------

// Start the session for user role and ID simulation
session_start();

// Simulate user login (REMOVE THIS IN A REAL APPLICATION!)
// This is just for testing purposes.
if (!isset($_SESSION['user_role'])) {
    //Example users:
    //Employee:
    //$_SESSION['user_role'] = $roles['employee']; // Employee Role
    //$_SESSION['user_id'] = 123; // Dummy User ID
    //Manager:
    //$_SESSION['user_role'] = $roles['manager']; // Manager Role
    //$_SESSION['user_id'] = 456; // Dummy User ID
    //Admin:
    //$_SESSION['user_role'] = $roles['admin']; // Admin Role
    //$_SESSION['user_id'] = 789; // Dummy User ID

    //To use, uncomment the above and refresh the page.  Then comment out after done using, otherwise
    //  it will reset the session every time you load the page.
    //  Real applications will have a login page and proper authentication.
    //  The simulation is here simply to make testing the role-based functionality easier.


}




echo "<h1>Expense Report System</h1>";

//User feedback messages.
if(isset($_SESSION['message'])) {
    echo "<p style='color: green;'>".$_SESSION['message']."</p>";
    unset($_SESSION['message']); //Clear the message after displaying it.
}
if(isset($_SESSION['error'])) {
    echo "<p style='color: red;'>".$_SESSION['error']."</p>";
    unset($_SESSION['error']);
}

// Display user role (for testing)
echo "<p>Current User Role: ";
$userRole = getCurrentUserRole();
if ($userRole !== null) {
    echo array_search($userRole, $roles); // Get role name from value
} else {
    echo "Not logged in";
}
echo "</p>";



// ------------------------------------------------------------------
//  Actions (Create, Read, Update, Delete, Approve)
// ------------------------------------------------------------------

// Create a new expense report (example)
if (isset($_POST['create_report'])) {
    $date = sanitizeInput($_POST['date']);
    $description = sanitizeInput($_POST['description']);
    $amount = floatval($_POST['amount']); // Convert to float
    $userId = getCurrentUserId();

    if ($userId) {
        $newReport = new ExpenseReport(0, $userId, $date, $description, $amount);
        if ($newReport->save($pdo)) {
            $_SESSION['message'] = "Expense report created successfully!";
            header("Location: ".$_SERVER['PHP_SELF']); // Redirect to refresh the page
            exit();
        } else {
            $_SESSION['error'] = "Failed to create expense report.";
            header("Location: ".$_SERVER['PHP_SELF']);
            exit();
        }
    } else {
        $_SESSION['error'] = "You must be logged in to create an expense report.";
        header("Location: ".$_SERVER['PHP_SELF']);
        exit();
    }
}


// Submit an existing report
if (isset($_POST['submit_report'])) {
    $reportId = (int)$_POST['report_id'];  // Get report ID from the form. Cast to int for safety.
    $userId = getCurrentUserId();

    if ($userId) {
        $report = ExpenseReport::getById($pdo, $reportId);

        if ($report && $report->getUserId() === $userId) { // Check if the user owns the report
            $report->setStatus("submitted");
            if ($report->save($pdo)) {
                $_SESSION['message'] = "Expense report submitted for approval!";
                header("Location: ".$_SERVER['PHP_SELF']);
                exit();
            } else {
                $_SESSION['error'] = "Failed to submit expense report.";
                header("Location: ".$_SERVER['PHP_SELF']);
                exit();
            }
        } else {
            $_SESSION['error'] = "You do not have permission to submit this expense report.";
            header("Location: ".$_SERVER['PHP_SELF']);
            exit();
        }
    } else {
         $_SESSION['error'] = "You must be logged in to submit an expense report.";
         header("Location: ".$_SERVER['PHP_SELF']);
         exit();
    }
}

// Approve an expense report
if (isset($_POST['approve_report'])) {
    $reportId = (int)$_POST['report_id'];
    $approverId = getCurrentUserId();
    $userRole = getCurrentUserRole();

    if ($approverId && $userRole) {
        $report = ExpenseReport::getById($pdo, $reportId);

        if ($report) {
            $currentStatus = $report->getStatus();
            if($currentStatus != "submitted") {
                $_SESSION['error'] = "Report must be in submitted state to be approved!";
                header("Location: ".$_SERVER['PHP_SELF']);
                exit();
            }

            // Check if the user's role is in the approval levels and if they haven't already approved it
            if (in_array($userRole, $approval_levels) && !in_array(["user_id" => $approverId], $report->getApprovalHistory())) {
                $report->addApproval($approverId, date("Y-m-d H:i:s"));

                // Check if all required approvals have been given
                $allApproved = true;
                foreach ($approval_levels as $level) {
                    $found = false;
                    foreach($report->getApprovalHistory() as $approval) {
                        if($approval['user_id'] == $level) {
                            $found = true;
                            break;
                        }
                    }
                    if(!$found) {
                        $allApproved = false;
                        break;
                    }
                }


                if ($allApproved) {
                    $report->setStatus("approved");
                }


                if ($report->save($pdo)) {
                    $_SESSION['message'] = "Expense report approved!";
                    header("Location: ".$_SERVER['PHP_SELF']);
                    exit();
                } else {
                    $_SESSION['error'] = "Failed to approve expense report.";
                    header("Location: ".$_SERVER['PHP_SELF']);
                    exit();
                }
            } else {
                $_SESSION['error'] = "You do not have permission to approve this expense report, or you have already approved it.";
                header("Location: ".$_SERVER['PHP_SELF']);
                exit();
            }
        } else {
            $_SESSION['error'] = "Expense report not found.";
            header("Location: ".$_SERVER['PHP_SELF']);
            exit();
        }
    } else {
        $_SESSION['error'] = "You must be logged in to approve expense reports.";
        header("Location: ".$_SERVER['PHP_SELF']);
        exit();
    }
}


// Reject an expense report
if (isset($_POST['reject_report'])) {
    $reportId = (int)$_POST['report_id'];
    $rejectorId = getCurrentUserId();
    $userRole = getCurrentUserRole();

    if ($rejectorId && $userRole) {
        $report = ExpenseReport::getById($pdo, $reportId);

        if ($report) {

            $currentStatus = $report->getStatus();
            if($currentStatus != "submitted") {
                $_SESSION['error'] = "Report must be in submitted state to be rejected!";
                header("Location: ".$_SERVER['PHP_SELF']);
                exit();
            }

            // Only managers or admins can reject
            if (hasRole($roles['manager'])) {
                $report->setStatus("rejected");
                if ($report->save($pdo)) {
                    $_SESSION['message'] = "Expense report rejected.";
                    header("Location: ".$_SERVER['PHP_SELF']);
                    exit();
                } else {
                    $_SESSION['error'] = "Failed to reject expense report.";
                    header("Location: ".$_SERVER['PHP_SELF']);
                    exit();
                }
            } else {
                $_SESSION['error'] = "You do not have permission to reject this expense report.";
                header("Location: ".$_SERVER['PHP_SELF']);
                exit();
            }
        } else {
            $_SESSION['error'] = "Expense report not found.";
            header("Location: ".$_SERVER['PHP_SELF']);
            exit();
        }
    } else {
        $_SESSION['error'] = "You must be logged in to reject expense reports.";
        header("Location: ".$_SERVER['PHP_SELF']);
        exit();
    }
}

// Delete an expense report
if (isset($_POST['delete_report'])) {
    $reportId = (int)$_POST['report_id'];
    $userId = getCurrentUserId();

    if ($userId) {
        $report = ExpenseReport::getById($pdo, $reportId);

        if ($report && $report->getUserId() === $userId) { // Check if the user owns the report
            if ($report->delete($pdo)) {
                $_SESSION['message'] = "Expense report deleted.";
                header("Location: ".$_SERVER['PHP_SELF']);
                exit();
            } else {
                $_SESSION['error'] = "Failed to delete expense report.";
                header("Location: ".$_SERVER['PHP_SELF']);
                exit();
            }
        } else {
            $_SESSION['error'] = "You do not have permission to delete this expense report.";
            header("Location: ".$_SERVER['PHP_SELF']);
            exit();
        }
    } else {
        $_SESSION['error'] = "You must be logged in to delete expense reports.";
        header("Location: ".$_SERVER['PHP_SELF']);
        exit();
    }
}


// ------------------------------------------------------------------
// Display Forms and Data
// ------------------------------------------------------------------

// Create Expense Report Form
echo "<h2>Create New Expense Report</h2>";
echo "<form method='post'>";
echo "  Date: <input type='date' name='date' required><br>";
echo "  Description: <input type='text' name='description' required><br>";
echo "  Amount: <input type='number' name='amount' step='0.01' required><br>";
echo "  <input type='submit' name='create_report' value='Create'>";
echo "</form><br>";


// List User's Expense Reports
echo "<h2>Your Expense Reports</h2>";
$userId = getCurrentUserId();
if ($userId) {
    $reports = ExpenseReport::getAllByUserId($pdo, $userId);

    if (count($reports) > 0) {
        echo "<table border='1'>";
        echo "<tr><th>ID</th><th>Date</th><th>Description</th><th>Amount</th><th>Status</th><th>Actions</th></tr>";
        foreach ($reports as $report) {
            echo "<tr>";
            echo "<td>" . htmlspecialchars($report->getId()) . "</td>";
            echo "<td>" . htmlspecialchars($report->getDate()) . "</td>";
            echo "<td>" . htmlspecialchars($report->getDescription()) . "</td>";
            echo "<td>" . htmlspecialchars($report->getAmount()) . "</td>";
            echo "<td>" . htmlspecialchars($report->getStatus()) . "</td>";
            echo "<td>";
            echo "<form method='post' style='display:inline;'>";
            echo "<input type='hidden' name='report_id' value='" . htmlspecialchars($report->getId()) . "'>";
            if ($report->getStatus() == "draft") {
                echo "<input type='submit' name='submit_report' value='Submit'>";
            }
            echo "</form>";
            echo "<form method='post' style='display:inline;'>";
            echo "<input type='hidden' name='report_id' value='" . htmlspecialchars($report->getId()) . "'>";
            echo "<input type='submit' name='delete_report' value='Delete' onclick='return confirm(\"Are you sure you want to delete this report?\");'>";
            echo "</form>";

            echo "</td>";
            echo "</tr>";
        }
        echo "</table>";
    } else {
        echo "No expense reports found.";
    }
    echo "<br>";
} else {
    echo "Please log in to view your expense reports.";
    echo "<br>";
}


// List Expense Reports Pending Approval (for Managers/Admins)
if (hasRole($roles['manager'])) {
    echo "<h2>Expense Reports Pending Your Approval</h2>";
    $pendingReports = ExpenseReport::getPendingApprovalByRole($pdo, $roles['manager']);

    if (count($pendingReports) > 0) {
        echo "<table border='1'>";
        echo "<tr><th>ID</th><th>User ID</th><th>Date</th><th>Description</th><th>Amount</th><th>Status</th><th>Actions</th></tr>";
        foreach ($pendingReports as $report) {
            echo "<tr>";
            echo "<td>" . htmlspecialchars($report->getId()) . "</td>";
            echo "<td>" . htmlspecialchars($report->getUserId()) . "</td>";
            echo "<td>" . htmlspecialchars($report->getDate()) . "</td>";
            echo "<td>" . htmlspecialchars($report->getDescription()) . "</td>";
            echo "<td>" . htmlspecialchars($report->getAmount()) . "</td>";
            echo "<td>" . htmlspecialchars($report->getStatus()) . "</td>";
            echo "<td>";
            echo "<form method='post' style='display:inline;'>";
            echo "<input type='hidden' name='report_id' value='" . htmlspecialchars($report->getId()) . "'>";
            echo "<input type='submit' name='approve_report' value='Approve'>";
            echo "</form>";
            echo "<form method='post' style='display:inline;'>";
            echo "<input type='hidden' name='report_id' value='" . htmlspecialchars($report->getId()) . "'>";
            echo "<input type='submit' name='reject_report' value='Reject'>";
            echo "</form>";
            echo "</td>";
            echo "</tr>";
        }
        echo "</table>";
    } else {
        echo "No expense reports pending your approval.";
    }
    echo "<br>";
}

?>
```

**Explanation:**

1.  **Configuration:**
    *   `$db_host`, `$db_name`, `$db_user`, `$db_pass`:  Database connection details.  **Important:**  Replace these with your actual database credentials.  **Never** hardcode sensitive information in a production environment. Use environment variables or a configuration file.
    *   `$roles`: Defines user roles with integer IDs (e.g., employee=1, manager=2, admin=3). This is a common pattern for role-based access control.  Use integer IDs for better database performance and easier comparison.
    *   `$approval_levels`: Defines the required approval levels for expense reports. The order determines the approval workflow.

2.  **Database Connection (PDO):**
    *   Uses PDO (PHP Data Objects) for database interaction.  PDO is a more secure and flexible way to connect to databases than the older `mysql_*` functions.
    *   Includes error handling using `try...catch` to gracefully handle database connection errors.
    *   `$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)`:  Sets PDO to throw exceptions on errors, which makes error handling easier and more robust.

3.  **Helper Functions:**
    *   `sanitizeInput()`:  **Crucial** for security. Sanitizes user input to prevent SQL injection and cross-site scripting (XSS) vulnerabilities.  Use `htmlspecialchars()` to escape HTML entities, and `trim()` to remove whitespace.
    *   `getCurrentUserRole()`:  Simulates getting the current user's role. **In a real application, you would replace this with your authentication system (e.g., using sessions, JWTs, etc.).** This function currently relies on `$_SESSION['user_role']` and `$_SESSION['user_id']`. This is purely for demonstration purposes and is **insecure** for production use.
    *   `getCurrentUserId()`:  Simulates getting the current user's ID. **Replace with your actual authentication logic.**
    *   `hasRole()`: Checks if the current user has a specific role, making role-based access control easier.  Allows higher roles to also perform actions of lower roles (e.g., an admin can approve reports).

4.  **ExpenseReport Class:**
    *   Represents an expense report object.
    *   Properties: `$id`, `$userId`, `$date`, `$description`, `$amount`, `$status`, `$approvalHistory`.
    *   `__construct()`:  The constructor initializes the object.  Note the default value for `$status` ("draft") when creating a new report.
    *   Getter methods: `getId()`, `getUserId()`, `getDate()`, `getDescription()`, `getAmount()`, `getStatus()`, `getApprovalHistory()`.
    *   Setter methods: `setStatus()`, `addApproval()`.
    *   `save(PDO $pdo)`: Saves the expense report to the database (either creates a new report if `$id` is 0, or updates an existing one).  Uses prepared statements to prevent SQL injection.  The `$approvalHistory` array is converted to a JSON string for storage in the database (assuming you have a `TEXT` or `JSON` column to store it).  After a successful insert, it fetches the last inserted ID to update the object's `$id` property.
    *   `delete(PDO $pdo)`: Deletes the expense report from the database. Uses a prepared statement.
    *   `getById(PDO $pdo, int $id)`: Retrieves an expense report from the database by its ID. Uses a prepared statement.  Converts the `approval_history` JSON string back into an array using `json_decode()`.  **Important:** Cast the database values to the correct data types (e.g., `(int)$row['id']`, `(float)$row['amount']`) for type safety.
    *   `getAllByUserId(PDO $pdo, int $userId)`: Retrieves all expense reports for a specific user ID.
    *   `getPendingApprovalByRole(PDO $pdo, int $roleId)`: Retrieves all expense reports that are in "submitted" status and are pending approval by a specific role.  This is the most complex query.  It uses `JSON_CONTAINS()` to check if the role ID exists in the `approval_history` JSON array.  This assumes your database supports JSON functions (MySQL 5.7.22+, MariaDB 10.2.1+).  If your database doesn't support JSON natively, you'll need to use a different approach (e.g., storing approval history in a separate table).

5.  **User Interface (Basic):**
    *   Uses simple HTML forms and output.  **This is just for demonstration and testing purposes.  You would replace this with a proper HTML/CSS/JavaScript user interface.**
    *   Displays user feedback messages (success/error) using `$_SESSION`. This is a simple way to pass messages between pages after a redirect.
    *   Simulates user login by setting `$_SESSION['user_role']` and `$_SESSION['user_id']`. **Remove this in a real application and implement proper authentication.**
    *   Displays the current user's role (for testing).

6.  **Actions (CRUD and Approval):**
    *   Handles form submissions for creating, submitting, approving, rejecting, and deleting expense reports.
    *   Uses `sanitizeInput()` to sanitize all user inputs before using them in database queries.
    *   Performs role-based access control checks using `hasRole()` to ensure that only authorized users can perform certain actions.
    *   Updates the status of expense reports based on the actions performed (e.g., "submitted", "approved", "rejected").
    *   Uses `header("Location: ".$_SERVER['PHP_SELF'])` to redirect back to the same page after an action is performed.  This helps prevent accidental form resubmissions.

7.  **Display Forms and Data:**
    *   Displays a form for creating new expense reports.
    *   Lists the current user's expense reports.
    *   Lists expense reports pending approval for managers/admins.

**Important Considerations and Improvements:**

*   **Security:**
    *   **Authentication:** Implement a proper authentication system (e.g., using usernames/passwords, OAuth, etc.) instead of relying on the session simulation.  Use password hashing (e.g., `password_hash()` and `password_verify()`) to store passwords securely.
    *   **Authorization:**  Thoroughly validate user permissions before allowing them to access or modify data.
    *   **SQL Injection:**  The code uses prepared statements, which is good, but always double-check your queries.
    *   **XSS:** The code uses `htmlspecialchars()`, which helps prevent XSS, but be mindful of where you're displaying user-generated content.
    *   **CSRF:** Protect against Cross-Site Request Forgery (CSRF) attacks by using CSRF tokens in your forms.
    *   **Input Validation:**  Implement robust input validation to ensure that user inputs are in the correct format and range.

*   **Database:**
    *   **Indexing:** Add indexes to your database tables (especially on `user_id`, `status`, and `id`) to improve query performance.
    *   **Data Types:**  Ensure that your database columns have the correct data types (e.g., `INT` for integers, `DECIMAL` for currency, `DATE` for dates).
    *   **JSON Support:**  If your database doesn't support JSON natively, you'll need to use a different approach for storing approval history (e.g., a separate table).  If you do use JSON, make sure to properly escape the data when storing it.
    *   **Transactions:**  Use database transactions to ensure that multiple database operations are performed atomically (either all succeed or all fail). This is especially important when updating multiple tables.
    *   **Database Abstraction Layer:** Consider using a database abstraction layer (e.g., Doctrine, Eloquent) to make your code more portable and easier to maintain.

*   **User Interface:**
    *   Use HTML, CSS, and JavaScript to create a more user-friendly interface.
    *   Use a templating engine (e.g., Twig, Blade) to separate your PHP code from your HTML code.
    *   Implement client-side validation to provide immediate feedback to users.
    *   Use AJAX to perform asynchronous operations (e.g., submitting an expense report without reloading the page).

*   **Workflow:**
    *   Implement email notifications to inform users when their expense reports have been submitted, approved, or rejected.
    *   Allow users to upload receipts and other supporting documents.
    *   Implement more sophisticated approval workflows (e.g., based on expense amount or department).
    *   Add audit logging to track all changes made to expense reports.

*   **Code Structure:**
    *   Use namespaces to organize your code into logical modules.
    *   Use dependency injection to make your code more testable and maintainable.
    *   Write unit tests to verify the correctness of your code.
    *   Use a code style guide (e.g., PSR-2) to ensure that your code is consistent and readable.
    *   Consider using an MVC (Model-View-Controller) framework (e.g., Laravel, Symfony) to structure your application.

*   **Error Handling:**
    *   Implement proper error logging to track errors and exceptions.
    *   Display user-friendly error messages to users.
    *   Use a debugging tool (e.g., Xdebug) to help you identify and fix errors.

*   **Scalability:**
    *   Use caching to improve performance.
    *   Use a load balancer to distribute traffic across multiple servers.
    *   Use a message queue to handle asynchronous tasks.

*   **Deployment:**
    *   Use a version control system (e.g., Git) to track changes to your code.
    *   Use a deployment tool (e.g., Capistrano, Deployer) to automate the deployment process.

This is a basic example, and a real-world application would require much more development and attention to detail. Remember to prioritize security and follow best practices to create a robust and reliable expense report system. Remember to replace the simulated authentication with a real authentication system.
👁️ Viewed: 4

Comments