Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
@ -1,13 +0,0 @@
|
|||||||
Plan:
|
|
||||||
1. **Investigate:** Checked Apache error logs and `includes/actions.php`.
|
|
||||||
2. **Identify Issue:** Found a mismatch between the database schema for `insurance_payments` (missing `payment_method` column) and the PHP code trying to insert into it.
|
|
||||||
3. **Fix:** Created and applied a migration to add the `payment_method` column to the `insurance_payments` table.
|
|
||||||
4. **Verify:** Confirmed the column now exists in the database.
|
|
||||||
|
|
||||||
Changed Files:
|
|
||||||
* `db/migrations/20260321_z_add_payment_method_to_insurance_payments.sql`: Added migration to fix the schema.
|
|
||||||
|
|
||||||
Next Steps:
|
|
||||||
* Go to **Insurance > Transactions**.
|
|
||||||
* Try adding a transaction again. It should work now.
|
|
||||||
* **Reminder:** Click Save in the editor to sync changes.
|
|
||||||
113
SETUP.md
113
SETUP.md
@ -1,113 +0,0 @@
|
|||||||
# Project Setup Guide
|
|
||||||
|
|
||||||
This guide provides step-by-step instructions to set up, configure, and run the Hospital Management System.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
* **PHP**: Version 8.0 or higher.
|
|
||||||
* **Database**: MariaDB 10.x or MySQL 5.7+.
|
|
||||||
* **Web Server**: Apache 2.4+ (with `mod_rewrite` enabled).
|
|
||||||
* **Composer**: (Optional) For dependency management if using third-party packages in the future.
|
|
||||||
|
|
||||||
## Installation Steps
|
|
||||||
|
|
||||||
1. **Clone the Repository**
|
|
||||||
```bash
|
|
||||||
git clone <repository_url>
|
|
||||||
cd <project_directory>
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Configure Web Server**
|
|
||||||
* Point your Apache VirtualHost `DocumentRoot` to the project directory.
|
|
||||||
* Ensure the directory has appropriate permissions (e.g., `www-data` user).
|
|
||||||
* Enable `.htaccess` overrides if applicable.
|
|
||||||
|
|
||||||
3. **Database Setup**
|
|
||||||
* Create a new MySQL/MariaDB database (e.g., `hospital_db`).
|
|
||||||
* Import the database schema and seed data. You can do this by running the initialization script or importing SQL files:
|
|
||||||
* **Option A (Script):** Run `php install.php` from the command line (ensure `db/config.php` is configured first).
|
|
||||||
* **Option B (Manual):** Import files from `db/migrations/` in chronological order using a tool like phpMyAdmin or CLI.
|
|
||||||
|
|
||||||
4. **Configuration Files**
|
|
||||||
* **Database:** Edit `db/config.php` to match your database credentials:
|
|
||||||
```php
|
|
||||||
define('DB_HOST', '127.0.0.1');
|
|
||||||
define('DB_NAME', 'your_db_name');
|
|
||||||
define('DB_USER', 'your_db_user');
|
|
||||||
define('DB_PASS', 'your_db_password');
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration & Environment Variables
|
|
||||||
|
|
||||||
The application uses a mix of PHP constants and environment variables for configuration.
|
|
||||||
|
|
||||||
### Email Configuration (`mail/config.php`)
|
|
||||||
The application uses PHPMailer. Configure the following environment variables (in your server config or `.env` file):
|
|
||||||
|
|
||||||
* `MAIL_TRANSPORT`: `smtp` (default)
|
|
||||||
* `SMTP_HOST`: e.g., `smtp.gmail.com`
|
|
||||||
* `SMTP_PORT`: e.g., `587`
|
|
||||||
* `SMTP_SECURE`: `tls` or `ssl`
|
|
||||||
* `SMTP_USER`: Your email address
|
|
||||||
* `SMTP_PASS`: Your email password or App Password
|
|
||||||
* `MAIL_FROM`: Sender email address
|
|
||||||
* `MAIL_FROM_NAME`: Sender name (e.g., "Hospital Admin")
|
|
||||||
|
|
||||||
### AI Integration (`ai/config.php`)
|
|
||||||
The system uses an AI proxy for features like "AI Suggestion" in visit forms and the Telegram bot.
|
|
||||||
* **Config File:** `ai/config.php`
|
|
||||||
* **Key Settings:** `base_url`, `project_uuid`.
|
|
||||||
* **Usage:** See `ai/LocalAIApi.php`.
|
|
||||||
|
|
||||||
### Telegram Integration
|
|
||||||
The system includes a Telegram bot for answering FAQs (`api/telegram_webhook.php`).
|
|
||||||
|
|
||||||
1. **Create a Bot:** Talk to `@BotFather` on Telegram to create a bot and get a **Token**.
|
|
||||||
2. **Save Token:** Insert the token into the `settings` table in your database:
|
|
||||||
```sql
|
|
||||||
INSERT INTO settings (setting_key, setting_value) VALUES ('telegram_token', 'YOUR_TELEGRAM_BOT_TOKEN');
|
|
||||||
```
|
|
||||||
3. **Set Webhook:** Configure the webhook URL for your bot:
|
|
||||||
```
|
|
||||||
https://api.telegram.org/bot<YOUR_TOKEN>/setWebhook?url=https://<YOUR_DOMAIN>/api/telegram_webhook.php
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integrations & Libraries
|
|
||||||
|
|
||||||
### Frontend (CDN)
|
|
||||||
The project relies on the following CDN-hosted libraries. Ensure you have an active internet connection.
|
|
||||||
|
|
||||||
* **Framework:** Bootstrap 5.3.0
|
|
||||||
* **Icons:** Bootstrap Icons 1.10.5
|
|
||||||
* **Fonts:** Google Fonts (Inter, Tajawal)
|
|
||||||
* **Forms:**
|
|
||||||
* Select2 4.1.0 (with Bootstrap 5 theme)
|
|
||||||
* Summernote Lite 0.8.18 (Rich Text Editor)
|
|
||||||
* Flatpickr (Date/Time picker)
|
|
||||||
* Inputmask 5.0.7 (Input formatting)
|
|
||||||
* **Utilities:**
|
|
||||||
* jQuery 3.6.0
|
|
||||||
* Ui-Avatars (User avatars)
|
|
||||||
* JsBarcode 3.11.5 (Patient labels)
|
|
||||||
|
|
||||||
### Backend Services
|
|
||||||
* **Email:** `mail/MailService.php` (Wraps PHPMailer).
|
|
||||||
* **AI:** `ai/LocalAIApi.php` (Handles OpenAI requests via proxy).
|
|
||||||
* **Reporting:** `includes/SimpleXLSX.php` (Excel export support).
|
|
||||||
|
|
||||||
## Default Credentials
|
|
||||||
|
|
||||||
If you used the provided migration scripts (`20260321_create_auth_system.sql`), the default administrator account is:
|
|
||||||
|
|
||||||
* **Email:** `admin@hospital.com`
|
|
||||||
* **Password:** `admin123`
|
|
||||||
* **Role:** Administrator
|
|
||||||
|
|
||||||
**Note:** Change this password immediately after logging in.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
* **Database Connection Error:** Check `db/config.php` credentials. Ensure the database server is running.
|
|
||||||
* **Email Not Sending:** Verify SMTP settings in `mail/config.php` or environment variables. Check server logs for connection timeouts.
|
|
||||||
* **AI Features Not Working:** Ensure the server can reach the AI proxy endpoint defined in `ai/config.php`.
|
|
||||||
* **Telegram Bot Not Responding:** Verify the `telegram_token` in the `settings` table and ensure the webhook URL is accessible from the public internet (HTTPS required).
|
|
||||||
@ -1,63 +1,41 @@
|
|||||||
<?php
|
<?php
|
||||||
// OpenAI proxy configuration (workspace scope).
|
// OpenAI proxy configuration (workspace scope).
|
||||||
// Reads values from environment variables or .env files.
|
// Reads values from environment variables or executor/.env.
|
||||||
|
|
||||||
// Helper to check env vars from multiple sources
|
$projectUuid = getenv('PROJECT_UUID');
|
||||||
function get_env_var($key) {
|
$projectId = getenv('PROJECT_ID');
|
||||||
$val = getenv($key);
|
|
||||||
if ($val !== false && $val !== '' && $val !== null) return $val;
|
|
||||||
if (isset($_SERVER[$key]) && $_SERVER[$key] !== '') return $_SERVER[$key];
|
|
||||||
if (isset($_ENV[$key]) && $_ENV[$key] !== '') return $_ENV[$key];
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$projectUuid = get_env_var('PROJECT_UUID');
|
if (
|
||||||
$projectId = get_env_var('PROJECT_ID');
|
($projectUuid === false || $projectUuid === null || $projectUuid === '') ||
|
||||||
|
($projectId === false || $projectId === null || $projectId === '')
|
||||||
// If missing, try loading from .env files
|
) {
|
||||||
if (!$projectUuid || !$projectId) {
|
$envPath = realpath(__DIR__ . '/../../.env'); // executor/.env
|
||||||
// List of possible .env locations (relative to this file)
|
if ($envPath && is_readable($envPath)) {
|
||||||
$possiblePaths = [
|
$lines = @file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||||
__DIR__ . '/../../.env', // executor/.env
|
foreach ($lines as $line) {
|
||||||
__DIR__ . '/../.env', // workspace/.env
|
$line = trim($line);
|
||||||
__DIR__ . '/.env', // ai/.env
|
if ($line === '' || $line[0] === '#') {
|
||||||
dirname(__DIR__, 2) . '/.env' // Alternative root
|
continue;
|
||||||
];
|
}
|
||||||
|
if (!str_contains($line, '=')) {
|
||||||
foreach ($possiblePaths as $path) {
|
continue;
|
||||||
$realPath = realpath($path);
|
}
|
||||||
if ($realPath && is_readable($realPath)) {
|
[$key, $value] = array_map('trim', explode('=', $line, 2));
|
||||||
$lines = @file($realPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
if ($key === '') {
|
||||||
foreach ($lines as $line) {
|
continue;
|
||||||
$line = trim($line);
|
}
|
||||||
if ($line === '' || $line[0] === '#') continue;
|
$value = trim($value, "\"' ");
|
||||||
if (!str_contains($line, '=')) continue;
|
if (getenv($key) === false || getenv($key) === '') {
|
||||||
|
putenv("{$key}={$value}");
|
||||||
[$key, $value] = array_map('trim', explode('=', $line, 2));
|
|
||||||
if ($key === '') continue;
|
|
||||||
|
|
||||||
$value = trim($value, "' ");
|
|
||||||
// Only set if not already set
|
|
||||||
if (!get_env_var($key)) {
|
|
||||||
putenv("{$key}={$value}");
|
|
||||||
$_SERVER[$key] = $value;
|
|
||||||
$_ENV[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Stop after first successful .env load
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
$projectUuid = getenv('PROJECT_UUID');
|
||||||
|
$projectId = getenv('PROJECT_ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh vars
|
|
||||||
$projectUuid = get_env_var('PROJECT_UUID');
|
|
||||||
$projectId = get_env_var('PROJECT_ID');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback if environment variables are completely missing in deployment
|
$projectUuid = ($projectUuid === false) ? null : $projectUuid;
|
||||||
// Using values from db/config.php logic or current environment
|
$projectId = ($projectId === false) ? null : $projectId;
|
||||||
if (!$projectUuid) $projectUuid = '36fb441e-8408-4101-afdc-7911dc065e36';
|
|
||||||
if (!$projectId) $projectId = '38960';
|
|
||||||
|
|
||||||
$baseUrl = 'https://flatlogic.com';
|
$baseUrl = 'https://flatlogic.com';
|
||||||
$responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null;
|
$responsesPath = $projectId ? "/projects/{$projectId}/ai-request" : null;
|
||||||
|
|||||||
@ -1,80 +0,0 @@
|
|||||||
<?php
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
require_once __DIR__ . '/../ai/LocalAIApi.php';
|
|
||||||
|
|
||||||
// Get JSON input
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
|
|
||||||
$target = $input['target'] ?? 'treatment_plan'; // symptoms, diagnosis, treatment_plan, translate
|
|
||||||
$symptoms = $input['symptoms'] ?? '';
|
|
||||||
$diagnosis = $input['diagnosis'] ?? '';
|
|
||||||
$currentValue = $input['current_value'] ?? ''; // For expanding symptoms or translation text
|
|
||||||
|
|
||||||
$systemPrompt = 'You are a professional medical assistant.';
|
|
||||||
$userPrompt = "";
|
|
||||||
|
|
||||||
switch ($target) {
|
|
||||||
case 'translate':
|
|
||||||
$text = $input['text'] ?? '';
|
|
||||||
$from = $input['from'] ?? 'English';
|
|
||||||
$to = $input['to'] ?? 'Arabic';
|
|
||||||
|
|
||||||
if (empty($text)) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'No text provided for translation.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$systemPrompt = "You are a professional translator specializing in medical terminology.";
|
|
||||||
$userPrompt = "Translate the following text from $from to $to. Return only the translated text without any explanations or extra characters.\n\nText: $text";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'symptoms':
|
|
||||||
if (empty($currentValue)) {
|
|
||||||
$userPrompt = "Generate a list of common clinical symptoms for a general checkup in a professional medical format (HTML lists).";
|
|
||||||
} else {
|
|
||||||
$userPrompt = "Rewrite and expand the following patient symptoms into a professional clinical description using HTML (bullet points or paragraph). Maintain the original meaning but make it clearer and more detailed:\n\n" . strip_tags($currentValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'diagnosis':
|
|
||||||
if (empty($symptoms)) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Please enter symptoms first to get a diagnosis suggestion.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$userPrompt = "Based on the following symptoms, suggest a list of potential differential diagnoses. Provide the response in a clear HTML list format.\n\nSymptoms:\n" . strip_tags($symptoms);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'treatment_plan':
|
|
||||||
default:
|
|
||||||
if (empty($symptoms) && empty($diagnosis)) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'No symptoms or diagnosis provided.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$userPrompt = "Based on the following symptoms and diagnosis, please generate a concise treatment plan and medical report for the patient.\n\n";
|
|
||||||
if (!empty($symptoms)) {
|
|
||||||
$userPrompt .= "Symptoms:\n" . strip_tags($symptoms) . "\n\n";
|
|
||||||
}
|
|
||||||
if (!empty($diagnosis)) {
|
|
||||||
$userPrompt .= "Diagnosis:\n" . strip_tags($diagnosis) . "\n\n";
|
|
||||||
}
|
|
||||||
$userPrompt .= "Please provide the report in a clear, professional format using HTML tags (like <ul>, <li>, <strong>, etc.) for better readability.";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = LocalAIApi::createResponse([
|
|
||||||
'input' => [
|
|
||||||
['role' => 'system', 'content' => $systemPrompt],
|
|
||||||
['role' => 'user', 'content' => $userPrompt],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!empty($response['success'])) {
|
|
||||||
$text = LocalAIApi::extractText($response);
|
|
||||||
echo json_encode(['success' => true, 'report' => trim($text)]);
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'error' => $response['error'] ?? 'AI generation failed.']);
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@ -1,359 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
require_once __DIR__ . '/../helpers.php';
|
|
||||||
|
|
||||||
// Prevent caching
|
|
||||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
|
||||||
header('Pragma: no-cache');
|
|
||||||
header('Expires: 0');
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
$db = db();
|
|
||||||
$lang = $_SESSION['lang'] ?? 'en';
|
|
||||||
$method = $_SERVER['REQUEST_METHOD'];
|
|
||||||
|
|
||||||
if ($method === 'GET') {
|
|
||||||
$id = $_GET['id'] ?? null;
|
|
||||||
if ($id) {
|
|
||||||
// Fetch single appointment
|
|
||||||
$stmt = $db->prepare("SELECT * FROM appointments WHERE id = ?");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
$appointment = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
echo json_encode($appointment);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$startStr = $_GET['start'] ?? null;
|
|
||||||
$endStr = $_GET['end'] ?? null;
|
|
||||||
$doctor_id = $_GET['doctor_id'] ?? null;
|
|
||||||
|
|
||||||
$events = [];
|
|
||||||
$businessHours = [];
|
|
||||||
|
|
||||||
// Fetch Appointments
|
|
||||||
$query = "
|
|
||||||
SELECT
|
|
||||||
a.id, a.start_time as start, a.end_time as end, a.reason, a.status,
|
|
||||||
a.patient_id, a.doctor_id, a.nurse_id, a.visit_type, a.address,
|
|
||||||
p.name as patient_name,
|
|
||||||
doc.name_$lang as doctor_name,
|
|
||||||
nur.name_$lang as nurse_name
|
|
||||||
FROM appointments a
|
|
||||||
JOIN patients p ON a.patient_id = p.id
|
|
||||||
LEFT JOIN employees doc ON a.doctor_id = doc.id
|
|
||||||
LEFT JOIN employees nur ON a.nurse_id = nur.id
|
|
||||||
WHERE 1=1";
|
|
||||||
|
|
||||||
$params = [];
|
|
||||||
if ($startStr) { $query .= " AND a.start_time >= ?"; $params[] = $startStr; }
|
|
||||||
if ($endStr) { $query .= " AND a.start_time <= ?"; $params[] = $endStr; }
|
|
||||||
if ($doctor_id) { $query .= " AND a.doctor_id = ?"; $params[] = $doctor_id; }
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $db->prepare($query);
|
|
||||||
$stmt->execute($params);
|
|
||||||
$appointments = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("Appointments Fetch Error: " . $e->getMessage());
|
|
||||||
$appointments = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($appointments as $a) {
|
|
||||||
$color = '#0d6efd'; // blue
|
|
||||||
if ($a['status'] === 'Completed') $color = '#198754'; // green
|
|
||||||
if ($a['status'] === 'Cancelled') $color = '#dc3545'; // red
|
|
||||||
if ($a['visit_type'] === 'Home') $color = '#fd7e14'; // orange for home visits
|
|
||||||
|
|
||||||
$providerName = $a['doctor_name'] ? $a['doctor_name'] : ($a['nurse_name'] . ' (' . __('nurse') . ')');
|
|
||||||
$title = $a['patient_name'] . ' - ' . $providerName;
|
|
||||||
if ($a['visit_type'] === 'Home') {
|
|
||||||
$title = '[🏠] ' . $title;
|
|
||||||
}
|
|
||||||
|
|
||||||
$events[] = [
|
|
||||||
'id' => $a['id'],
|
|
||||||
'title' => $title,
|
|
||||||
'start' => $a['start'],
|
|
||||||
'end' => $a['end'],
|
|
||||||
'color' => $color,
|
|
||||||
'extendedProps' => [
|
|
||||||
'type' => 'appointment',
|
|
||||||
'patient_id' => $a['patient_id'],
|
|
||||||
'doctor_id' => $a['doctor_id'],
|
|
||||||
'nurse_id' => $a['nurse_id'],
|
|
||||||
'visit_type' => $a['visit_type'],
|
|
||||||
'address' => $a['address'],
|
|
||||||
'patient_name' => $a['patient_name'],
|
|
||||||
'doctor_name' => $a['doctor_name'],
|
|
||||||
'nurse_name' => $a['nurse_name'],
|
|
||||||
'status' => $a['status'],
|
|
||||||
'reason' => $a['reason']
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch Public Holidays
|
|
||||||
$holidayQuery = "SELECT holiday_date as start, name_$lang as title FROM holidays WHERE 1=1";
|
|
||||||
$holidayParams = [];
|
|
||||||
if ($startStr) { $holidayQuery .= " AND holiday_date >= ?"; $holidayParams[] = date('Y-m-d', strtotime($startStr)); }
|
|
||||||
if ($endStr) { $holidayQuery .= " AND holiday_date <= ?"; $holidayParams[] = date('Y-m-d', strtotime($endStr)); }
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $db->prepare($holidayQuery);
|
|
||||||
$stmt->execute($holidayParams);
|
|
||||||
$holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$holidays = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$s = get_system_settings();
|
|
||||||
$global_start = $s['working_hours_start'] ?? '08:00';
|
|
||||||
$global_end = $s['working_hours_end'] ?? '17:00';
|
|
||||||
|
|
||||||
foreach ($holidays as $h) {
|
|
||||||
// Render a visible block event in the time grid
|
|
||||||
$events[] = [
|
|
||||||
'id' => 'hol_block_' . $h['start'],
|
|
||||||
'title' => __('holiday') . ': ' . $h['title'],
|
|
||||||
'start' => $h['start'] . 'T' . $global_start . ':00',
|
|
||||||
'end' => $h['start'] . 'T' . $global_end . ':00',
|
|
||||||
'allDay' => false,
|
|
||||||
'color' => '#dc3545',
|
|
||||||
'textColor' => '#fff',
|
|
||||||
'className' => 'public-holiday-event',
|
|
||||||
'extendedProps' => ['type' => 'public_holiday_block', 'blocks_selection' => true]
|
|
||||||
];
|
|
||||||
|
|
||||||
// Render daily blocks for time grid
|
|
||||||
$events[] = [
|
|
||||||
'id' => 'hol_bg_' . $h['start'],
|
|
||||||
'start' => $h['start'] . 'T00:00:00',
|
|
||||||
'end' => $h['start'] . 'T23:59:59',
|
|
||||||
'allDay' => false,
|
|
||||||
'display' => 'background',
|
|
||||||
'backgroundColor' => 'rgba(255, 193, 7, 0.5)',
|
|
||||||
'overlap' => false,
|
|
||||||
'extendedProps' => ['type' => 'public_holiday', 'blocks_selection' => true]
|
|
||||||
];
|
|
||||||
// Visible event strip across the top
|
|
||||||
$events[] = [
|
|
||||||
'id' => 'hol_title_' . $h['start'],
|
|
||||||
'title' => __('holiday') . ': ' . $h['title'],
|
|
||||||
'start' => $h['start'],
|
|
||||||
'end' => $h['start'],
|
|
||||||
'allDay' => true,
|
|
||||||
'color' => '#ffc107',
|
|
||||||
'textColor' => '#000',
|
|
||||||
'extendedProps' => ['type' => 'public_holiday']
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch Doctor Holidays (from Leave Requests)
|
|
||||||
// Updated to join employees instead of doctors
|
|
||||||
$docHolidayQuery = "
|
|
||||||
SELECT lr.id, lr.start_date, lr.end_date, lr.reason as note,
|
|
||||||
lr.employee_id as doctor_id,
|
|
||||||
e.name_$lang as doctor_name
|
|
||||||
FROM leave_requests lr
|
|
||||||
JOIN employees e ON lr.employee_id = e.id
|
|
||||||
WHERE lr.status = 'Approved'";
|
|
||||||
|
|
||||||
$docHolidayParams = [];
|
|
||||||
|
|
||||||
// Date filtering for doctor holidays (ranges)
|
|
||||||
if ($startStr && $endStr) {
|
|
||||||
$docHolidayQuery .= " AND lr.start_date <= ? AND lr.end_date >= ?";
|
|
||||||
$docHolidayParams[] = date('Y-m-d', strtotime($endStr));
|
|
||||||
$docHolidayParams[] = date('Y-m-d', strtotime($startStr));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($doctor_id) {
|
|
||||||
$docHolidayQuery .= " AND lr.employee_id = ?";
|
|
||||||
$docHolidayParams[] = $doctor_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $db->prepare($docHolidayQuery);
|
|
||||||
$stmt->execute($docHolidayParams);
|
|
||||||
$docHolidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$docHolidays = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($docHolidays as $dh) {
|
|
||||||
$title = $dh['doctor_name'] . ' - ' . ($dh['note'] ?: 'Holiday');
|
|
||||||
$endDayStr = date('Y-m-d', strtotime($dh['end_date'] . ' +1 day'));
|
|
||||||
|
|
||||||
$currentDate = strtotime($dh['start_date']);
|
|
||||||
$endDate = strtotime($dh['end_date']);
|
|
||||||
|
|
||||||
$isFilteredDoctor = ($doctor_id && $doctor_id == $dh['doctor_id']);
|
|
||||||
|
|
||||||
// Output background events for each day individually
|
|
||||||
while ($currentDate <= $endDate) {
|
|
||||||
$dateStr = date('Y-m-d', $currentDate);
|
|
||||||
|
|
||||||
$events[] = [
|
|
||||||
'id' => 'doc_hol_block_' . $dh['id'] . '_' . $dateStr,
|
|
||||||
'title' => 'Holiday: ' . $dh['doctor_name'],
|
|
||||||
'start' => $dateStr . 'T' . $global_start . ':00',
|
|
||||||
'end' => $dateStr . 'T' . $global_end . ':00',
|
|
||||||
'allDay' => false,
|
|
||||||
'color' => '#ffc107',
|
|
||||||
'textColor' => '#000',
|
|
||||||
'className' => 'doctor-holiday-event',
|
|
||||||
'extendedProps' => ['type' => 'doctor_holiday_block', 'doctor_id' => $dh['doctor_id'], 'blocks_selection' => $isFilteredDoctor]
|
|
||||||
];
|
|
||||||
|
|
||||||
$events[] = [
|
|
||||||
'id' => 'doc_hol_bg_' . $dh['id'] . '_' . $dateStr,
|
|
||||||
'start' => $dateStr . 'T00:00:00',
|
|
||||||
'end' => $dateStr . 'T23:59:59',
|
|
||||||
'display' => 'background',
|
|
||||||
'allDay' => false,
|
|
||||||
'backgroundColor' => $isFilteredDoctor ? 'rgba(255, 193, 7, 0.5)' : 'rgba(255, 193, 7, 0.15)',
|
|
||||||
'overlap' => !$isFilteredDoctor,
|
|
||||||
'extendedProps' => ['type' => 'doctor_holiday', 'doctor_id' => $dh['doctor_id'], 'blocks_selection' => $isFilteredDoctor]
|
|
||||||
];
|
|
||||||
|
|
||||||
$currentDate = strtotime('+1 day', $currentDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
$events[] = [
|
|
||||||
'id' => 'doc_hol_' . $dh['id'],
|
|
||||||
'title' => $title,
|
|
||||||
'start' => $dh['start_date'],
|
|
||||||
'end' => $endDayStr,
|
|
||||||
'allDay' => true,
|
|
||||||
'color' => '#ffc107',
|
|
||||||
'textColor' => '#000',
|
|
||||||
'extendedProps' => ['type' => 'doctor_holiday', 'doctor_id' => $dh['doctor_id'], 'blocks_selection' => $isFilteredDoctor]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set Business Hours (Global Default since individual schedules are removed)
|
|
||||||
$st = $s['working_hours_start'] ?? '08:00';
|
|
||||||
$et = $s['working_hours_end'] ?? '17:00';
|
|
||||||
$businessHours = [
|
|
||||||
[
|
|
||||||
'daysOfWeek' => [0, 1, 2, 3, 4, 5, 6],
|
|
||||||
'startTime' => $st,
|
|
||||||
'endTime' => $et
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'events' => $events,
|
|
||||||
'businessHours' => $businessHours,
|
|
||||||
'settings' => [
|
|
||||||
'working_hours_start' => $global_start,
|
|
||||||
'working_hours_end' => $global_end
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkDoctorHoliday($db, $doctor_id, $start_time) {
|
|
||||||
if (!$doctor_id || !$start_time) return false;
|
|
||||||
$date = date('Y-m-d', strtotime($start_time));
|
|
||||||
try {
|
|
||||||
// Query leave_requests directly using employee_id (which is $doctor_id)
|
|
||||||
$stmt = $db->prepare("SELECT COUNT(*) FROM leave_requests WHERE employee_id = ? AND status = 'Approved' AND ? BETWEEN start_date AND end_date");
|
|
||||||
$stmt->execute([$doctor_id, $date]);
|
|
||||||
return $stmt->fetchColumn() > 0;
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
error_log("Check Holiday Error: " . $e->getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($method === 'POST') {
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
if (!$input) {
|
|
||||||
$input = $_POST;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($input)) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'No input data received']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$action = $input['action'] ?? '';
|
|
||||||
|
|
||||||
if (empty($action)) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'No action specified']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($action === 'create') {
|
|
||||||
$patient_id = $input['patient_id'] ?? '';
|
|
||||||
$doctor_id = $input['doctor_id'] ?: null; // Nullable
|
|
||||||
$nurse_id = $input['nurse_id'] ?: null; // Nullable
|
|
||||||
$visit_type = $input['visit_type'] ?? 'Clinic';
|
|
||||||
$address = $input['address'] ?? '';
|
|
||||||
$start_time = $input['start_time'] ?? '';
|
|
||||||
$reason = $input['reason'] ?? '';
|
|
||||||
|
|
||||||
if ($patient_id && ($doctor_id || $nurse_id) && $start_time) {
|
|
||||||
// Check for holiday conflict if doctor assigned
|
|
||||||
if ($doctor_id && checkDoctorHoliday($db, $doctor_id, $start_time)) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Doctor is on holiday on this date.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $db->prepare("INSERT INTO appointments (patient_id, doctor_id, nurse_id, visit_type, address, start_time, end_time, reason) VALUES (?, ?, ?, ?, ?, ?, DATE_ADD(?, INTERVAL 30 MINUTE), ?)");
|
|
||||||
$stmt->execute([$patient_id, $doctor_id, $nurse_id, $visit_type, $address, $start_time, $start_time, $reason]);
|
|
||||||
echo json_encode(['success' => true, 'id' => $db->lastInsertId()]);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'DB Error: ' . $e->getMessage()]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing fields']);
|
|
||||||
}
|
|
||||||
} elseif ($action === 'update') {
|
|
||||||
$id = $input['id'] ?? '';
|
|
||||||
$patient_id = $input['patient_id'] ?? '';
|
|
||||||
$doctor_id = $input['doctor_id'] ?: null;
|
|
||||||
$nurse_id = $input['nurse_id'] ?: null;
|
|
||||||
$visit_type = $input['visit_type'] ?? 'Clinic';
|
|
||||||
$address = $input['address'] ?? '';
|
|
||||||
$start_time = $input['start_time'] ?? '';
|
|
||||||
$status = $input['status'] ?? 'Scheduled';
|
|
||||||
$reason = $input['reason'] ?? '';
|
|
||||||
|
|
||||||
if ($id && $patient_id && ($doctor_id || $nurse_id) && $start_time) {
|
|
||||||
// Check for holiday conflict
|
|
||||||
if ($doctor_id && checkDoctorHoliday($db, $doctor_id, $start_time)) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Doctor is on holiday on this date.']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $db->prepare("UPDATE appointments SET patient_id = ?, doctor_id = ?, nurse_id = ?, visit_type = ?, address = ?, start_time = ?, end_time = DATE_ADD(?, INTERVAL 30 MINUTE), status = ?, reason = ? WHERE id = ?");
|
|
||||||
$stmt->execute([$patient_id, $doctor_id, $nurse_id, $visit_type, $address, $start_time, $start_time, $status, $reason, $id]);
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'DB Error: ' . $e->getMessage()]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing fields']);
|
|
||||||
}
|
|
||||||
} elseif ($action === 'delete') {
|
|
||||||
$id = $input['id'] ?? '';
|
|
||||||
if ($id) {
|
|
||||||
try {
|
|
||||||
$stmt = $db->prepare("DELETE FROM appointments WHERE id = ?");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'DB Error: ' . $e->getMessage()]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing ID']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid action']);
|
|
||||||
}
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
274
api/billing.php
274
api/billing.php
@ -1,274 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
$action = $_POST['action'] ?? $_GET['action'] ?? '';
|
|
||||||
|
|
||||||
if ($action === 'get_bill_details') {
|
|
||||||
$visit_id = $_GET['visit_id'] ?? 0;
|
|
||||||
$bill_id_param = $_GET['bill_id'] ?? 0;
|
|
||||||
|
|
||||||
if (!$visit_id && !$bill_id_param) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Visit ID or Bill ID required']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($bill_id_param) {
|
|
||||||
$stmt = $db->prepare("SELECT visit_id FROM bills WHERE id = ?");
|
|
||||||
$stmt->execute([$bill_id_param]);
|
|
||||||
$visit_id = $stmt->fetchColumn();
|
|
||||||
|
|
||||||
if (!$visit_id) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Bill not found']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Get Visit & Patient Info
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT v.*, p.name as patient_name, p.insurance_company_id, ic.name_en as insurance_name
|
|
||||||
FROM visits v
|
|
||||||
JOIN patients p ON v.patient_id = p.id
|
|
||||||
LEFT JOIN insurance_companies ic ON p.insurance_company_id = ic.id
|
|
||||||
WHERE v.id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$visit_id]);
|
|
||||||
$visit = $stmt->fetch();
|
|
||||||
|
|
||||||
if (!$visit) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Visit not found']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Get or Create Bill
|
|
||||||
$stmt = $db->prepare("SELECT * FROM bills WHERE visit_id = ?");
|
|
||||||
$stmt->execute([$visit_id]);
|
|
||||||
$bill = $stmt->fetch();
|
|
||||||
|
|
||||||
if (!$bill) {
|
|
||||||
$stmt = $db->prepare("INSERT INTO bills (patient_id, visit_id, status, created_at) VALUES (?, ?, 'Pending', NOW())");
|
|
||||||
$stmt->execute([$visit['patient_id'], $visit_id]);
|
|
||||||
$bill_id = $db->lastInsertId();
|
|
||||||
|
|
||||||
// Re-fetch
|
|
||||||
$stmt = $db->prepare("SELECT * FROM bills WHERE id = ?");
|
|
||||||
$stmt->execute([$bill_id]);
|
|
||||||
$bill = $stmt->fetch();
|
|
||||||
} else {
|
|
||||||
$bill_id = $bill['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- AUTO-ADD ITEMS FROM OTHER DEPARTMENTS ---
|
|
||||||
// Fetch existing items to prevent duplicates
|
|
||||||
$stmt = $db->prepare("SELECT description FROM bill_items WHERE bill_id = ?");
|
|
||||||
$stmt->execute([$bill_id]);
|
|
||||||
$existing_items = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
||||||
|
|
||||||
// Helper to check and add
|
|
||||||
$added_count = 0;
|
|
||||||
|
|
||||||
// 1. X-Rays
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT xt.name_en, xt.price
|
|
||||||
FROM xray_inquiry_items xii
|
|
||||||
JOIN xray_inquiries xi ON xii.inquiry_id = xi.id
|
|
||||||
JOIN xray_tests xt ON xii.xray_id = xt.id
|
|
||||||
WHERE xi.visit_id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$visit_id]);
|
|
||||||
$xrays = $stmt->fetchAll();
|
|
||||||
|
|
||||||
foreach ($xrays as $x) {
|
|
||||||
$desc = "X-Ray: " . $x['name_en'];
|
|
||||||
if (!in_array($desc, $existing_items)) {
|
|
||||||
$stmt = $db->prepare("INSERT INTO bill_items (bill_id, description, amount) VALUES (?, ?, ?)");
|
|
||||||
$stmt->execute([$bill_id, $desc, $x['price']]);
|
|
||||||
$existing_items[] = $desc; // Update local cache
|
|
||||||
$added_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Labs
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT lt.name_en, lt.price
|
|
||||||
FROM inquiry_tests it
|
|
||||||
JOIN laboratory_inquiries li ON it.inquiry_id = li.id
|
|
||||||
JOIN laboratory_tests lt ON it.test_id = lt.id
|
|
||||||
WHERE li.visit_id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$visit_id]);
|
|
||||||
$labs = $stmt->fetchAll();
|
|
||||||
|
|
||||||
foreach ($labs as $l) {
|
|
||||||
$desc = "Lab: " . $l['name_en'];
|
|
||||||
if (!in_array($desc, $existing_items)) {
|
|
||||||
$stmt = $db->prepare("INSERT INTO bill_items (bill_id, description, amount) VALUES (?, ?, ?)");
|
|
||||||
$stmt->execute([$bill_id, $desc, $l['price']]);
|
|
||||||
$existing_items[] = $desc;
|
|
||||||
$added_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Drugs (Prescriptions)
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT vp.drug_name, d.price
|
|
||||||
FROM visit_prescriptions vp
|
|
||||||
LEFT JOIN drugs d ON (vp.drug_name = d.name_en OR vp.drug_name = d.name_ar)
|
|
||||||
WHERE vp.visit_id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$visit_id]);
|
|
||||||
$drugs = $stmt->fetchAll();
|
|
||||||
|
|
||||||
foreach ($drugs as $d) {
|
|
||||||
$price = $d['price'] ?: 0; // Default to 0 if not found
|
|
||||||
$desc = "Pharmacy: " . $d['drug_name'];
|
|
||||||
if (!in_array($desc, $existing_items)) {
|
|
||||||
$stmt = $db->prepare("INSERT INTO bill_items (bill_id, description, amount) VALUES (?, ?, ?)");
|
|
||||||
$stmt->execute([$bill_id, $desc, $price]);
|
|
||||||
$existing_items[] = $desc;
|
|
||||||
$added_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If items were added, update the bill total
|
|
||||||
if ($added_count > 0) {
|
|
||||||
updateBillTotal($db, $bill_id);
|
|
||||||
// Re-fetch bill to get updated total if needed (though calculate total below handles it)
|
|
||||||
$stmt = $db->prepare("SELECT * FROM bills WHERE id = ?");
|
|
||||||
$stmt->execute([$bill_id]);
|
|
||||||
$bill = $stmt->fetch();
|
|
||||||
}
|
|
||||||
// ---------------------------------------------
|
|
||||||
|
|
||||||
// 3. Get Bill Items (Fresh)
|
|
||||||
$stmt = $db->prepare("SELECT * FROM bill_items WHERE bill_id = ?");
|
|
||||||
$stmt->execute([$bill['id']]);
|
|
||||||
$items = $stmt->fetchAll();
|
|
||||||
|
|
||||||
// 4. Calculate Totals (if not synced)
|
|
||||||
$total = 0;
|
|
||||||
foreach ($items as $item) {
|
|
||||||
$total += $item['amount'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return Data
|
|
||||||
echo json_encode([
|
|
||||||
'success' => true,
|
|
||||||
'visit' => $visit,
|
|
||||||
'bill' => $bill,
|
|
||||||
'items' => $items,
|
|
||||||
'calculated_total' => $total,
|
|
||||||
'has_insurance' => !empty($visit['insurance_company_id']),
|
|
||||||
'insurance_name' => $visit['insurance_name']
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elseif ($action === 'add_item') {
|
|
||||||
$bill_id = $_POST['bill_id'] ?? 0;
|
|
||||||
$description = $_POST['description'] ?? '';
|
|
||||||
$amount = $_POST['amount'] ?? 0;
|
|
||||||
|
|
||||||
if (!$bill_id || !$description || !$amount) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing fields']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $db->prepare("INSERT INTO bill_items (bill_id, description, amount) VALUES (?, ?, ?)");
|
|
||||||
$stmt->execute([$bill_id, $description, $amount]);
|
|
||||||
|
|
||||||
// Update Bill Total
|
|
||||||
updateBillTotal($db, $bill_id);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elseif ($action === 'remove_item') {
|
|
||||||
$item_id = $_POST['item_id'] ?? 0;
|
|
||||||
|
|
||||||
if (!$item_id) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Item ID required']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get bill_id first
|
|
||||||
$stmt = $db->prepare("SELECT bill_id FROM bill_items WHERE id = ?");
|
|
||||||
$stmt->execute([$item_id]);
|
|
||||||
$row = $stmt->fetch();
|
|
||||||
|
|
||||||
if ($row) {
|
|
||||||
$stmt = $db->prepare("DELETE FROM bill_items WHERE id = ?");
|
|
||||||
$stmt->execute([$item_id]);
|
|
||||||
updateBillTotal($db, $row['bill_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elseif ($action === 'update_totals') {
|
|
||||||
$bill_id = $_POST['bill_id'] ?? 0;
|
|
||||||
$insurance_covered = $_POST['insurance_covered'] ?? 0;
|
|
||||||
$patient_payable = $_POST['patient_payable'] ?? 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $db->prepare("UPDATE bills SET insurance_covered = ?, patient_payable = ? WHERE id = ?");
|
|
||||||
$stmt->execute([$insurance_covered, $patient_payable, $bill_id]);
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elseif ($action === 'complete_payment') {
|
|
||||||
$bill_id = $_POST['bill_id'] ?? 0;
|
|
||||||
$payment_method = $_POST['payment_method'] ?? 'Cash';
|
|
||||||
$notes = $_POST['notes'] ?? '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
$db->beginTransaction();
|
|
||||||
|
|
||||||
// Update Bill
|
|
||||||
$stmt = $db->prepare("UPDATE bills SET status = 'Paid', payment_method = ?, notes = ? WHERE id = ?");
|
|
||||||
$stmt->execute([$payment_method, $notes, $bill_id]);
|
|
||||||
|
|
||||||
// Get Visit ID
|
|
||||||
$stmt = $db->prepare("SELECT visit_id FROM bills WHERE id = ?");
|
|
||||||
$stmt->execute([$bill_id]);
|
|
||||||
$bill = $stmt->fetch();
|
|
||||||
|
|
||||||
// Update Visit
|
|
||||||
if ($bill && $bill['visit_id']) {
|
|
||||||
$stmt = $db->prepare("UPDATE visits SET status = 'Completed', checkout_time = NOW() WHERE id = ?");
|
|
||||||
$stmt->execute([$bill['visit_id']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$db->commit();
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$db->rollBack();
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBillTotal($db, $bill_id) {
|
|
||||||
$stmt = $db->prepare("SELECT SUM(amount) FROM bill_items WHERE bill_id = ?");
|
|
||||||
$stmt->execute([$bill_id]);
|
|
||||||
$total = $stmt->fetchColumn() ?: 0;
|
|
||||||
|
|
||||||
$stmt = $db->prepare("UPDATE bills SET total_amount = ? WHERE id = ?");
|
|
||||||
$stmt->execute([$total, $bill_id]);
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
<?php
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pdo = db();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Database connection failed']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read input
|
|
||||||
$input = file_get_contents('php://input');
|
|
||||||
$data = json_decode($input, true);
|
|
||||||
|
|
||||||
if (!$data) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid JSON']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic Auth (API Key)
|
|
||||||
// In production, check against biometric_devices table
|
|
||||||
$api_key = $data['api_key'] ?? '';
|
|
||||||
if ($api_key !== 'test_key') {
|
|
||||||
// Check DB
|
|
||||||
$stmt = $pdo->prepare("SELECT id FROM biometric_devices WHERE api_key = ? AND status = 1");
|
|
||||||
$stmt->execute([$api_key]);
|
|
||||||
if (!$stmt->fetch()) {
|
|
||||||
http_response_code(401);
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid API Key']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Data
|
|
||||||
$employee_id = $data['employee_id'] ?? null;
|
|
||||||
$timestamp = $data['timestamp'] ?? date('Y-m-d H:i:s'); // ISO 8601 or Y-m-d H:i:s
|
|
||||||
$type = $data['type'] ?? 'check_in'; // check_in or check_out
|
|
||||||
|
|
||||||
if (!$employee_id) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing employee_id']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine status based on time (simple logic)
|
|
||||||
$time = date('H:i:s', strtotime($timestamp));
|
|
||||||
$date = date('Y-m-d', strtotime($timestamp));
|
|
||||||
$status = 'Present';
|
|
||||||
if ($type === 'check_in' && $time > '09:00:00') {
|
|
||||||
$status = 'Late';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO attendance_logs (employee_id, date, check_in, check_out, status, source) VALUES (?, ?, ?, ?, ?, 'Biometric Device')");
|
|
||||||
|
|
||||||
$check_in = ($type === 'check_in') ? date('Y-m-d H:i:s', strtotime($timestamp)) : null;
|
|
||||||
$check_out = ($type === 'check_out') ? date('Y-m-d H:i:s', strtotime($timestamp)) : null;
|
|
||||||
|
|
||||||
// Check if entry exists for this date to update instead of insert?
|
|
||||||
// For simplicity, we just insert logs. A real system might merge them.
|
|
||||||
// Let's try to find an existing log for today
|
|
||||||
$existing = $pdo->prepare("SELECT id FROM attendance_logs WHERE employee_id = ? AND date = ? ORDER BY id DESC LIMIT 1");
|
|
||||||
$existing->execute([$employee_id, $date]);
|
|
||||||
$log = $existing->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($log) {
|
|
||||||
if ($type === 'check_in') {
|
|
||||||
// Maybe they checked in again? Update check_in if null
|
|
||||||
$upd = $pdo->prepare("UPDATE attendance_logs SET check_in = ? WHERE id = ? AND check_in IS NULL");
|
|
||||||
$upd->execute([$check_in, $log['id']]);
|
|
||||||
} else {
|
|
||||||
// Check out
|
|
||||||
$upd = $pdo->prepare("UPDATE attendance_logs SET check_out = ? WHERE id = ?");
|
|
||||||
$upd->execute([$check_out, $log['id']]);
|
|
||||||
}
|
|
||||||
$msg = "Updated existing log";
|
|
||||||
} else {
|
|
||||||
$stmt->execute([$employee_id, $date, $check_in, $check_out, $status]);
|
|
||||||
$msg = "Created new log";
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'message' => $msg]);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
require_once __DIR__ . '/../helpers.php';
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
$db = db();
|
|
||||||
$method = $_SERVER['REQUEST_METHOD'];
|
|
||||||
|
|
||||||
if ($method === 'POST') {
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
|
||||||
$action = $input['action'] ?? '';
|
|
||||||
|
|
||||||
if ($action === 'add_city') {
|
|
||||||
$name_en = $input['name_en'] ?? '';
|
|
||||||
$name_ar = $input['name_ar'] ?? '';
|
|
||||||
|
|
||||||
if ($name_en && $name_ar) {
|
|
||||||
try {
|
|
||||||
$stmt = $db->prepare("INSERT INTO cities (name_en, name_ar) VALUES (?, ?)");
|
|
||||||
$stmt->execute([$name_en, $name_ar]);
|
|
||||||
$id = $db->lastInsertId();
|
|
||||||
echo json_encode(['success' => true, 'id' => $id, 'name_en' => $name_en, 'name_ar' => $name_ar]);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing fields']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid action']);
|
|
||||||
}
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Invalid request method']);
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
require_once __DIR__ . '/../helpers.php';
|
|
||||||
|
|
||||||
// Prevent caching
|
|
||||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
|
||||||
header('Pragma: no-cache');
|
|
||||||
header('Expires: 0');
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
$db = db();
|
|
||||||
$doctor_id = $_GET['doctor_id'] ?? null;
|
|
||||||
|
|
||||||
if (!$doctor_id) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing doctor_id']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// $doctor_id is expected to be employee_id now
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT start_date, end_date, reason
|
|
||||||
FROM leave_requests
|
|
||||||
WHERE employee_id = ? AND status = 'Approved' AND end_date >= CURDATE()
|
|
||||||
");
|
|
||||||
$stmt->execute([$doctor_id]);
|
|
||||||
$holidays = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'holidays' => $holidays]);
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'DB Error: ' . $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
require_once __DIR__ . '/../helpers.php';
|
|
||||||
require_once __DIR__ . '/../includes/auth.php';
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
// Check if user is logged in
|
|
||||||
if (!isset($_SESSION['user_id'])) {
|
|
||||||
http_response_code(401);
|
|
||||||
echo json_encode(['error' => 'Unauthorized']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check permission
|
|
||||||
if (!has_permission('inventory')) {
|
|
||||||
http_response_code(403);
|
|
||||||
echo json_encode(['error' => 'Forbidden: Stock Management permission required']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? '';
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
if ($action === 'get_batches') {
|
|
||||||
$item_id = $_GET['item_id'] ?? 0;
|
|
||||||
if (!$item_id) {
|
|
||||||
echo json_encode([]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $db->prepare("SELECT id, batch_number, quantity, expiry_date, cost_price FROM inventory_batches WHERE item_id = ? AND quantity > 0 ORDER BY expiry_date ASC");
|
|
||||||
$stmt->execute([$item_id]);
|
|
||||||
$batches = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
echo json_encode($batches);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(['error' => 'Invalid action']);
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? '';
|
|
||||||
$pdo = db();
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch ($action) {
|
|
||||||
case 'search':
|
|
||||||
$q = $_GET['q'] ?? '';
|
|
||||||
|
|
||||||
if (empty($q)) {
|
|
||||||
echo json_encode(['results' => []]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search by name, phone or id (patient number)
|
|
||||||
$sql = "SELECT id, name, phone, civil_id FROM patients WHERE name LIKE ? OR phone LIKE ? OR civil_id LIKE ? OR id = ? LIMIT 20";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$term = "%$q%";
|
|
||||||
$id_term = intval($q); // for exact match on patient number
|
|
||||||
$stmt->execute([$term, $term, $term, $id_term]);
|
|
||||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Format results for select2
|
|
||||||
$formatted_results = array_map(function($p) {
|
|
||||||
$patient_number = sprintf('%06d', $p['id']);
|
|
||||||
$display_text = $patient_number . ' - ' . $p['name'];
|
|
||||||
if (!empty($p['phone'])) {
|
|
||||||
$display_text .= ' - ' . $p['phone'];
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
'id' => $p['id'],
|
|
||||||
'text' => $display_text,
|
|
||||||
'name' => $p['name'],
|
|
||||||
'phone' => $p['phone']
|
|
||||||
];
|
|
||||||
}, $results);
|
|
||||||
|
|
||||||
echo json_encode(['results' => $formatted_results]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['error' => 'Invalid action']);
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
483
api/pharmacy.php
483
api/pharmacy.php
@ -1,483 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? '';
|
|
||||||
$pdo = db();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
|
||||||
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 10;
|
|
||||||
$offset = ($page - 1) * $limit;
|
|
||||||
|
|
||||||
switch ($action) {
|
|
||||||
case 'get_stock':
|
|
||||||
// List all drugs with total stock quantity
|
|
||||||
$sql = "SELECT d.id, d.name_en, d.name_ar, d.min_stock_level, d.reorder_level, d.unit,
|
|
||||||
COALESCE(SUM(b.quantity), 0) as total_stock
|
|
||||||
FROM drugs d
|
|
||||||
LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE()
|
|
||||||
GROUP BY d.id
|
|
||||||
ORDER BY d.name_en ASC";
|
|
||||||
$stmt = $pdo->query($sql);
|
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'get_low_stock':
|
|
||||||
// Count total for pagination
|
|
||||||
$countSql = "SELECT COUNT(*) FROM (
|
|
||||||
SELECT d.id
|
|
||||||
FROM drugs d
|
|
||||||
LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE()
|
|
||||||
GROUP BY d.id
|
|
||||||
HAVING COALESCE(SUM(b.quantity), 0) <= MAX(d.reorder_level)
|
|
||||||
) as total";
|
|
||||||
$total = $pdo->query($countSql)->fetchColumn();
|
|
||||||
|
|
||||||
// List drugs where total stock is <= reorder_level
|
|
||||||
$sql = "SELECT d.id, d.name_en, d.name_ar, d.min_stock_level, d.reorder_level, d.unit,
|
|
||||||
COALESCE(SUM(b.quantity), 0) as total_stock
|
|
||||||
FROM drugs d
|
|
||||||
LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE()
|
|
||||||
GROUP BY d.id
|
|
||||||
HAVING total_stock <= MAX(d.reorder_level)
|
|
||||||
ORDER BY total_stock ASC
|
|
||||||
LIMIT :limit OFFSET :offset";
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'get_expired':
|
|
||||||
// Count total
|
|
||||||
$countSql = "SELECT COUNT(*)
|
|
||||||
FROM pharmacy_batches b
|
|
||||||
JOIN drugs d ON b.drug_id = d.id
|
|
||||||
LEFT JOIN suppliers s ON b.supplier_id = s.id
|
|
||||||
WHERE b.expiry_date < CURDATE() AND b.quantity > 0";
|
|
||||||
$total = $pdo->query($countSql)->fetchColumn();
|
|
||||||
|
|
||||||
// List batches that have expired and still have quantity
|
|
||||||
$sql = "SELECT b.id, b.batch_number, b.expiry_date, b.quantity,
|
|
||||||
d.name_en as drug_name, d.name_ar as drug_name_ar,
|
|
||||||
s.name_en as supplier_name
|
|
||||||
FROM pharmacy_batches b
|
|
||||||
JOIN drugs d ON b.drug_id = d.id
|
|
||||||
LEFT JOIN suppliers s ON b.supplier_id = s.id
|
|
||||||
WHERE b.expiry_date < CURDATE() AND b.quantity > 0
|
|
||||||
ORDER BY b.expiry_date ASC
|
|
||||||
LIMIT :limit OFFSET :offset";
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'get_near_expiry':
|
|
||||||
$days = $_GET['days'] ?? 90;
|
|
||||||
|
|
||||||
// Count total
|
|
||||||
$countSql = "SELECT COUNT(*)
|
|
||||||
FROM pharmacy_batches b
|
|
||||||
JOIN drugs d ON b.drug_id = d.id
|
|
||||||
LEFT JOIN suppliers s ON b.supplier_id = s.id
|
|
||||||
WHERE b.expiry_date >= CURDATE()
|
|
||||||
AND b.expiry_date <= DATE_ADD(CURDATE(), INTERVAL ? DAY)
|
|
||||||
AND b.quantity > 0";
|
|
||||||
$countStmt = $pdo->prepare($countSql);
|
|
||||||
$countStmt->execute([$days]);
|
|
||||||
$total = $countStmt->fetchColumn();
|
|
||||||
|
|
||||||
// List batches expiring in the next X days
|
|
||||||
$sql = "SELECT b.id, b.batch_number, b.expiry_date, b.quantity,
|
|
||||||
d.name_en as drug_name, d.name_ar as drug_name_ar,
|
|
||||||
s.name_en as supplier_name,
|
|
||||||
DATEDIFF(b.expiry_date, CURDATE()) as days_remaining
|
|
||||||
FROM pharmacy_batches b
|
|
||||||
JOIN drugs d ON b.drug_id = d.id
|
|
||||||
LEFT JOIN suppliers s ON b.supplier_id = s.id
|
|
||||||
WHERE b.expiry_date >= CURDATE()
|
|
||||||
AND b.expiry_date <= DATE_ADD(CURDATE(), INTERVAL :days DAY)
|
|
||||||
AND b.quantity > 0
|
|
||||||
ORDER BY b.expiry_date ASC
|
|
||||||
LIMIT :limit OFFSET :offset";
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->bindValue(':days', $days, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit
|
|
||||||
]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'get_batches':
|
|
||||||
$drug_id = $_GET['drug_id'] ?? 0;
|
|
||||||
if (!$drug_id) throw new Exception("Drug ID required");
|
|
||||||
|
|
||||||
$sql = "SELECT b.*, s.name_en as supplier_name
|
|
||||||
FROM pharmacy_batches b
|
|
||||||
LEFT JOIN suppliers s ON b.supplier_id = s.id
|
|
||||||
WHERE b.drug_id = ? AND b.quantity > 0
|
|
||||||
ORDER BY b.expiry_date ASC";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute([$drug_id]);
|
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'add_stock':
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Exception("Invalid method");
|
|
||||||
|
|
||||||
$drug_id = $_POST['drug_id'] ?? 0;
|
|
||||||
$batch_number = $_POST['batch_number'] ?? '';
|
|
||||||
$expiry_date = $_POST['expiry_date'] ?? '';
|
|
||||||
$quantity = $_POST['quantity'] ?? 0;
|
|
||||||
$cost_price = $_POST['cost_price'] ?? 0;
|
|
||||||
$sale_price = $_POST['sale_price'] ?? 0;
|
|
||||||
$supplier_id = !empty($_POST['supplier_id']) ? $_POST['supplier_id'] : null;
|
|
||||||
|
|
||||||
if (!$drug_id || !$batch_number || !$expiry_date || !$quantity) {
|
|
||||||
throw new Exception("Missing required fields");
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO pharmacy_batches
|
|
||||||
(drug_id, batch_number, expiry_date, quantity, cost_price, sale_price, supplier_id, received_date)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, CURDATE())");
|
|
||||||
$stmt->execute([$drug_id, $batch_number, $expiry_date, $quantity, $cost_price, $sale_price, $supplier_id]);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Stock added successfully']);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'search_drugs':
|
|
||||||
$q = $_GET['q'] ?? '';
|
|
||||||
$sql = "SELECT d.id, d.name_en, d.name_ar, d.sku, d.price as default_price,
|
|
||||||
(SELECT sale_price FROM pharmacy_batches pb WHERE pb.drug_id = d.id AND pb.quantity > 0 AND pb.expiry_date >= CURDATE() ORDER BY pb.expiry_date ASC LIMIT 1) as batch_price,
|
|
||||||
COALESCE(SUM(b.quantity), 0) as stock
|
|
||||||
FROM drugs d
|
|
||||||
LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE()
|
|
||||||
WHERE (d.name_en LIKE ? OR d.name_ar LIKE ? OR d.sku LIKE ?)
|
|
||||||
GROUP BY d.id
|
|
||||||
LIMIT 20";
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$term = "%$q%";
|
|
||||||
$stmt->execute([$term, $term, $term]);
|
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'create_sale':
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Exception("Invalid method");
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
|
|
||||||
if (empty($input['items'])) throw new Exception("No items in sale");
|
|
||||||
|
|
||||||
// Check Settings
|
|
||||||
$allow_negative = false;
|
|
||||||
try {
|
|
||||||
$settingStmt = $pdo->prepare("SELECT setting_value FROM settings WHERE setting_key = 'allow_negative_stock'");
|
|
||||||
$settingStmt->execute();
|
|
||||||
$allow_negative = (bool)$settingStmt->fetchColumn();
|
|
||||||
} catch (Exception $e) { /* ignore */ }
|
|
||||||
|
|
||||||
$pdo->beginTransaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create Sale Record
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO pharmacy_sales (patient_id, visit_id, total_amount, payment_method, status) VALUES (?, ?, ?, ?, 'completed')");
|
|
||||||
$stmt->execute([
|
|
||||||
$input['patient_id'] ?? null,
|
|
||||||
$input['visit_id'] ?? null,
|
|
||||||
$input['total_amount'] ?? 0,
|
|
||||||
$input['payment_method'] ?? 'cash'
|
|
||||||
]);
|
|
||||||
$sale_id = $pdo->lastInsertId();
|
|
||||||
|
|
||||||
// Process Items
|
|
||||||
foreach ($input['items'] as $item) {
|
|
||||||
$drug_id = $item['drug_id'];
|
|
||||||
$qty_needed = $item['quantity'];
|
|
||||||
$unit_price = $item['price']; // Or fetch from batch? Use provided price for now.
|
|
||||||
|
|
||||||
// Fetch available batches (FIFO)
|
|
||||||
$batch_stmt = $pdo->prepare("SELECT id, quantity FROM pharmacy_batches WHERE drug_id = ? AND quantity > 0 ORDER BY expiry_date ASC FOR UPDATE");
|
|
||||||
$batch_stmt->execute([$drug_id]);
|
|
||||||
$batches = $batch_stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$qty_remaining = $qty_needed;
|
|
||||||
|
|
||||||
foreach ($batches as $batch) {
|
|
||||||
if ($qty_remaining <= 0) break;
|
|
||||||
|
|
||||||
$take = min($batch['quantity'], $qty_remaining);
|
|
||||||
|
|
||||||
// Deduct from batch
|
|
||||||
$update = $pdo->prepare("UPDATE pharmacy_batches SET quantity = quantity - ? WHERE id = ?");
|
|
||||||
$update->execute([$take, $batch['id']]);
|
|
||||||
|
|
||||||
// Add to sale items
|
|
||||||
$item_stmt = $pdo->prepare("INSERT INTO pharmacy_sale_items (sale_id, drug_id, batch_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?, ?)");
|
|
||||||
$item_stmt->execute([$sale_id, $drug_id, $batch['id'], $take, $unit_price, $take * $unit_price]);
|
|
||||||
|
|
||||||
$qty_remaining -= $take;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($qty_remaining > 0) {
|
|
||||||
if ($allow_negative) {
|
|
||||||
// Record sale for remaining quantity without batch
|
|
||||||
$item_stmt = $pdo->prepare("INSERT INTO pharmacy_sale_items (sale_id, drug_id, batch_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?, ?)");
|
|
||||||
$item_stmt->execute([$sale_id, $drug_id, null, $qty_remaining, $unit_price, $qty_remaining * $unit_price]);
|
|
||||||
} else {
|
|
||||||
throw new Exception("Insufficient stock for drug ID: $drug_id");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo->commit();
|
|
||||||
echo json_encode(['success' => true, 'sale_id' => $sale_id]);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$pdo->rollBack();
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'get_sales':
|
|
||||||
// List recent sales
|
|
||||||
$sql = "SELECT s.*, p.name as patient_name
|
|
||||||
FROM pharmacy_sales s
|
|
||||||
LEFT JOIN patients p ON s.patient_id = p.id
|
|
||||||
ORDER BY s.created_at DESC LIMIT 50";
|
|
||||||
$stmt = $pdo->query($sql);
|
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'get_sale_details':
|
|
||||||
$sale_id = $_GET['sale_id'] ?? 0;
|
|
||||||
if (!$sale_id) throw new Exception("Sale ID required");
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("SELECT s.*, p.name as patient_name FROM pharmacy_sales s LEFT JOIN patients p ON s.patient_id = p.id WHERE s.id = ?");
|
|
||||||
$stmt->execute([$sale_id]);
|
|
||||||
$sale = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$sale) throw new Exception("Sale not found");
|
|
||||||
|
|
||||||
$items_stmt = $pdo->prepare("SELECT i.*, d.name_en as drug_name
|
|
||||||
FROM pharmacy_sale_items i
|
|
||||||
JOIN drugs d ON i.drug_id = d.id
|
|
||||||
WHERE i.sale_id = ?");
|
|
||||||
$items_stmt->execute([$sale_id]);
|
|
||||||
$sale['items'] = $items_stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
echo json_encode($sale);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'get_report':
|
|
||||||
$type = $_GET['type'] ?? 'inventory_valuation';
|
|
||||||
$startDate = $_GET['start_date'] ?? date('Y-m-01');
|
|
||||||
$endDate = $_GET['end_date'] ?? date('Y-m-d');
|
|
||||||
|
|
||||||
if ($type === 'inventory_valuation') {
|
|
||||||
// Count distinct drugs in stock for pagination
|
|
||||||
$countSql = "SELECT COUNT(DISTINCT d.id)
|
|
||||||
FROM drugs d
|
|
||||||
JOIN pharmacy_batches b ON d.id = b.drug_id
|
|
||||||
WHERE b.quantity > 0";
|
|
||||||
$total = $pdo->query($countSql)->fetchColumn();
|
|
||||||
|
|
||||||
$sql = "SELECT d.name_en as drug_name, d.name_ar as drug_name_ar,
|
|
||||||
g.name_en as category_name,
|
|
||||||
SUM(b.quantity) as stock_quantity,
|
|
||||||
SUM(b.quantity * b.cost_price) / SUM(b.quantity) as avg_cost,
|
|
||||||
SUM(b.quantity * b.sale_price) / SUM(b.quantity) as selling_price,
|
|
||||||
SUM(b.quantity * b.cost_price) as total_cost_value,
|
|
||||||
SUM(b.quantity * b.sale_price) as total_sales_value
|
|
||||||
FROM drugs d
|
|
||||||
JOIN pharmacy_batches b ON d.id = b.drug_id
|
|
||||||
LEFT JOIN drugs_groups g ON d.group_id = g.id
|
|
||||||
WHERE b.quantity > 0
|
|
||||||
GROUP BY d.id
|
|
||||||
ORDER BY d.name_en ASC
|
|
||||||
LIMIT :limit OFFSET :offset";
|
|
||||||
|
|
||||||
// Calculate Grand Totals (entire stock)
|
|
||||||
$grandTotalSql = "SELECT SUM(b.quantity * b.cost_price) as total_cost,
|
|
||||||
SUM(b.quantity * b.sale_price) as total_sales
|
|
||||||
FROM pharmacy_batches b
|
|
||||||
WHERE b.quantity > 0";
|
|
||||||
$grandTotals = $pdo->query($grandTotalSql)->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit,
|
|
||||||
'grand_total_cost' => $grandTotals['total_cost'] ?? 0,
|
|
||||||
'grand_total_sales' => $grandTotals['total_sales'] ?? 0
|
|
||||||
]);
|
|
||||||
|
|
||||||
} elseif ($type === 'sales') {
|
|
||||||
// Count
|
|
||||||
$countSql = "SELECT COUNT(*) FROM pharmacy_sales WHERE created_at BETWEEN ? AND ? + INTERVAL 1 DAY";
|
|
||||||
$countStmt = $pdo->prepare($countSql);
|
|
||||||
$countStmt->execute([$startDate, $endDate]);
|
|
||||||
$total = $countStmt->fetchColumn();
|
|
||||||
|
|
||||||
$sql = "SELECT s.id, s.created_at, s.total_amount, s.payment_method,
|
|
||||||
p.name as patient_name,
|
|
||||||
(SELECT COUNT(*) FROM pharmacy_sale_items i WHERE i.sale_id = s.id) as item_count
|
|
||||||
FROM pharmacy_sales s
|
|
||||||
LEFT JOIN patients p ON s.patient_id = p.id
|
|
||||||
WHERE s.created_at BETWEEN :start AND :end + INTERVAL 1 DAY
|
|
||||||
ORDER BY s.created_at DESC
|
|
||||||
LIMIT :limit OFFSET :offset";
|
|
||||||
|
|
||||||
$grandTotalSql = "SELECT SUM(total_amount) as total FROM pharmacy_sales WHERE created_at BETWEEN ? AND ? + INTERVAL 1 DAY";
|
|
||||||
$grandTotalStmt = $pdo->prepare($grandTotalSql);
|
|
||||||
$grandTotalStmt->execute([$startDate, $endDate]);
|
|
||||||
$grandTotal = $grandTotalStmt->fetchColumn();
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->bindValue(':start', $startDate);
|
|
||||||
$stmt->bindValue(':end', $endDate);
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit,
|
|
||||||
'grand_total_sales' => $grandTotal ?? 0
|
|
||||||
]);
|
|
||||||
|
|
||||||
} elseif ($type === 'expiry') {
|
|
||||||
$countSql = "SELECT COUNT(*) FROM pharmacy_batches WHERE expiry_date BETWEEN ? AND ? AND quantity > 0";
|
|
||||||
$countStmt = $pdo->prepare($countSql);
|
|
||||||
$countStmt->execute([$startDate, $endDate]);
|
|
||||||
$total = $countStmt->fetchColumn();
|
|
||||||
|
|
||||||
$sql = "SELECT b.id, b.batch_number, b.expiry_date, b.quantity,
|
|
||||||
d.name_en as drug_name, d.name_ar as drug_name_ar,
|
|
||||||
s.name_en as supplier_name,
|
|
||||||
DATEDIFF(b.expiry_date, CURDATE()) as days_remaining
|
|
||||||
FROM pharmacy_batches b
|
|
||||||
JOIN drugs d ON b.drug_id = d.id
|
|
||||||
LEFT JOIN suppliers s ON b.supplier_id = s.id
|
|
||||||
WHERE b.expiry_date BETWEEN :start AND :end AND b.quantity > 0
|
|
||||||
ORDER BY b.expiry_date ASC
|
|
||||||
LIMIT :limit OFFSET :offset";
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->bindValue(':start', $startDate);
|
|
||||||
$stmt->bindValue(':end', $endDate);
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit
|
|
||||||
]);
|
|
||||||
|
|
||||||
} elseif ($type === 'purchase_report') {
|
|
||||||
$countSql = "SELECT COUNT(*) FROM pharmacy_lpos WHERE lpo_date BETWEEN ? AND ?";
|
|
||||||
$countStmt = $pdo->prepare($countSql);
|
|
||||||
$countStmt->execute([$startDate, $endDate]);
|
|
||||||
$total = $countStmt->fetchColumn();
|
|
||||||
|
|
||||||
$sql = "SELECT l.id, l.lpo_date, l.status, l.total_amount, s.name_en as supplier_name, s.name_ar as supplier_name_ar
|
|
||||||
FROM pharmacy_lpos l
|
|
||||||
LEFT JOIN suppliers s ON l.supplier_id = s.id
|
|
||||||
WHERE l.lpo_date BETWEEN :start AND :end
|
|
||||||
ORDER BY l.lpo_date DESC
|
|
||||||
LIMIT :limit OFFSET :offset";
|
|
||||||
|
|
||||||
$grandTotalSql = "SELECT SUM(total_amount) as total FROM pharmacy_lpos WHERE lpo_date BETWEEN ? AND ?";
|
|
||||||
$grandTotalStmt = $pdo->prepare($grandTotalSql);
|
|
||||||
$grandTotalStmt->execute([$startDate, $endDate]);
|
|
||||||
$grandTotal = $grandTotalStmt->fetchColumn();
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->bindValue(':start', $startDate);
|
|
||||||
$stmt->bindValue(':end', $endDate);
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit,
|
|
||||||
'grand_total_purchases' => $grandTotal ?? 0
|
|
||||||
]);
|
|
||||||
} elseif ($type === 'low_stock') {
|
|
||||||
// Reuse get_low_stock logic
|
|
||||||
$countSql = "SELECT COUNT(*) FROM (
|
|
||||||
SELECT d.id
|
|
||||||
FROM drugs d
|
|
||||||
LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE()
|
|
||||||
GROUP BY d.id
|
|
||||||
HAVING COALESCE(SUM(b.quantity), 0) <= MAX(d.reorder_level)
|
|
||||||
) as total";
|
|
||||||
$total = $pdo->query($countSql)->fetchColumn();
|
|
||||||
|
|
||||||
$sql = "SELECT d.id, d.name_en, d.name_ar, d.min_stock_level, d.reorder_level, d.unit,
|
|
||||||
COALESCE(SUM(b.quantity), 0) as total_stock
|
|
||||||
FROM drugs d
|
|
||||||
LEFT JOIN pharmacy_batches b ON d.id = b.drug_id AND b.quantity > 0 AND b.expiry_date >= CURDATE()
|
|
||||||
GROUP BY d.id
|
|
||||||
HAVING total_stock <= MAX(d.reorder_level)
|
|
||||||
ORDER BY total_stock ASC
|
|
||||||
LIMIT :limit OFFSET :offset";
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Exception("Invalid action");
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@ -1,282 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
$pdo = db();
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
if ($action === 'create_lpo') {
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
|
||||||
|
|
||||||
if (empty($data['supplier_id']) || empty($data['items'])) {
|
|
||||||
throw new Exception("Supplier and items are required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo->beginTransaction();
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO pharmacy_lpos (supplier_id, lpo_date, status, total_amount, notes) VALUES (?, ?, 'Draft', ?, ?)");
|
|
||||||
$stmt->execute([
|
|
||||||
$data['supplier_id'],
|
|
||||||
$data['lpo_date'] ?? date('Y-m-d'),
|
|
||||||
$data['total_amount'] ?? 0,
|
|
||||||
$data['notes'] ?? ''
|
|
||||||
]);
|
|
||||||
$lpoId = $pdo->lastInsertId();
|
|
||||||
|
|
||||||
$stmtItem = $pdo->prepare("INSERT INTO pharmacy_lpo_items (lpo_id, drug_id, quantity, cost_price, total_cost, batch_number, expiry_date) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
|
||||||
|
|
||||||
foreach ($data['items'] as $item) {
|
|
||||||
$stmtItem->execute([
|
|
||||||
$lpoId,
|
|
||||||
$item['drug_id'],
|
|
||||||
$item['quantity'],
|
|
||||||
$item['cost_price'],
|
|
||||||
$item['total_cost'],
|
|
||||||
!empty($item['batch_number']) ? $item['batch_number'] : null,
|
|
||||||
!empty($item['expiry_date']) ? $item['expiry_date'] : null
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo->commit();
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Purchase created successfully']);
|
|
||||||
|
|
||||||
} elseif ($action === 'update_status') {
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
|
||||||
if (empty($data['id']) || empty($data['status'])) {
|
|
||||||
throw new Exception("ID and Status are required");
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo->beginTransaction();
|
|
||||||
|
|
||||||
// Check if items update is requested (specifically for Received status or corrections)
|
|
||||||
if (!empty($data['items']) && is_array($data['items'])) {
|
|
||||||
$updateItemStmt = $pdo->prepare("UPDATE pharmacy_lpo_items SET batch_number = ?, expiry_date = ? WHERE id = ?");
|
|
||||||
foreach ($data['items'] as $item) {
|
|
||||||
if (!empty($item['id'])) {
|
|
||||||
$updateItemStmt->execute([
|
|
||||||
!empty($item['batch_number']) ? $item['batch_number'] : null,
|
|
||||||
!empty($item['expiry_date']) ? $item['expiry_date'] : null,
|
|
||||||
$item['id']
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If status is being changed to Received, we must update stock
|
|
||||||
if ($data['status'] === 'Received') {
|
|
||||||
// Fetch LPO items (re-fetch to get updated values)
|
|
||||||
$stmtItems = $pdo->prepare("SELECT * FROM pharmacy_lpo_items WHERE lpo_id = ?");
|
|
||||||
$stmtItems->execute([$data['id']]);
|
|
||||||
$items = $stmtItems->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Fetch LPO details for supplier
|
|
||||||
$stmtLPO = $pdo->prepare("SELECT supplier_id FROM pharmacy_lpos WHERE id = ?");
|
|
||||||
$stmtLPO->execute([$data['id']]);
|
|
||||||
$lpo = $stmtLPO->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
$batchStmt = $pdo->prepare("INSERT INTO pharmacy_batches (drug_id, batch_number, expiry_date, quantity, cost_price, sale_price, supplier_id, received_date) VALUES (?, ?, ?, ?, ?, ?, ?, CURDATE())");
|
|
||||||
|
|
||||||
// We need sale price for the batch. Ideally, LPO should have it or we fetch current from drugs table.
|
|
||||||
$drugPriceStmt = $pdo->prepare("SELECT price FROM drugs WHERE id = ?");
|
|
||||||
|
|
||||||
foreach ($items as $item) {
|
|
||||||
if (empty($item['batch_number']) || empty($item['expiry_date'])) {
|
|
||||||
// If still missing (should be caught by UI), generate defaults
|
|
||||||
$item['batch_number'] = $item['batch_number'] ?? 'BATCH-' . date('Ymd') . '-' . $item['id'];
|
|
||||||
$item['expiry_date'] = $item['expiry_date'] ?? date('Y-m-d', strtotime('+1 year'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$drugPriceStmt->execute([$item['drug_id']]);
|
|
||||||
$drug = $drugPriceStmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
$salePrice = $drug['price'] ?? ($item['cost_price'] * 1.5); // Fallback margin
|
|
||||||
|
|
||||||
$batchStmt->execute([
|
|
||||||
$item['drug_id'],
|
|
||||||
$item['batch_number'],
|
|
||||||
$item['expiry_date'],
|
|
||||||
$item['quantity'],
|
|
||||||
$item['cost_price'],
|
|
||||||
$salePrice,
|
|
||||||
$lpo['supplier_id']
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("UPDATE pharmacy_lpos SET status = ? WHERE id = ?");
|
|
||||||
$stmt->execute([$data['status'], $data['id']]);
|
|
||||||
|
|
||||||
$pdo->commit();
|
|
||||||
echo json_encode(['success' => true]);
|
|
||||||
|
|
||||||
} elseif ($action === 'create_return') {
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
|
||||||
|
|
||||||
if (empty($data['supplier_id']) || empty($data['items'])) {
|
|
||||||
throw new Exception("Supplier and items are required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo->beginTransaction();
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO pharmacy_purchase_returns (supplier_id, return_date, total_amount, reason) VALUES (?, ?, ?, ?)");
|
|
||||||
$stmt->execute([
|
|
||||||
$data['supplier_id'],
|
|
||||||
$data['return_date'] ?? date('Y-m-d'),
|
|
||||||
$data['total_amount'] ?? 0,
|
|
||||||
$data['reason'] ?? ''
|
|
||||||
]);
|
|
||||||
$returnId = $pdo->lastInsertId();
|
|
||||||
|
|
||||||
$stmtItem = $pdo->prepare("INSERT INTO pharmacy_purchase_return_items (return_id, drug_id, batch_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?, ?)");
|
|
||||||
$updateBatch = $pdo->prepare("UPDATE pharmacy_batches SET quantity = quantity - ? WHERE id = ?");
|
|
||||||
|
|
||||||
foreach ($data['items'] as $item) {
|
|
||||||
// Check stock first
|
|
||||||
$checkBatch = $pdo->prepare("SELECT quantity FROM pharmacy_batches WHERE id = ?");
|
|
||||||
$checkBatch->execute([$item['batch_id']]);
|
|
||||||
$currentStock = $checkBatch->fetchColumn();
|
|
||||||
|
|
||||||
if ($currentStock < $item['quantity']) {
|
|
||||||
throw new Exception("Insufficient stock in batch for drug ID " . $item['drug_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmtItem->execute([
|
|
||||||
$returnId,
|
|
||||||
$item['drug_id'],
|
|
||||||
$item['batch_id'],
|
|
||||||
$item['quantity'],
|
|
||||||
$item['unit_price'],
|
|
||||||
$item['total_price']
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Deduct stock
|
|
||||||
$updateBatch->execute([$item['quantity'], $item['batch_id']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo->commit();
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Return created successfully']);
|
|
||||||
}
|
|
||||||
} elseif ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|
||||||
if ($action === 'get_lpos') {
|
|
||||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
|
||||||
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20;
|
|
||||||
$offset = ($page - 1) * $limit;
|
|
||||||
|
|
||||||
// Count total
|
|
||||||
$countStmt = $pdo->query("SELECT COUNT(*) FROM pharmacy_lpos");
|
|
||||||
$total = $countStmt->fetchColumn();
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("
|
|
||||||
SELECT l.*, s.name_en as supplier_name
|
|
||||||
FROM pharmacy_lpos l
|
|
||||||
LEFT JOIN suppliers s ON l.supplier_id = s.id
|
|
||||||
ORDER BY l.created_at DESC
|
|
||||||
LIMIT :limit OFFSET :offset
|
|
||||||
");
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit,
|
|
||||||
'pages' => ceil($total / $limit)
|
|
||||||
]);
|
|
||||||
|
|
||||||
} elseif ($action === 'get_lpo_details') {
|
|
||||||
$id = $_GET['id'] ?? 0;
|
|
||||||
$stmt = $pdo->prepare("
|
|
||||||
SELECT i.*, d.name_en as drug_name, d.sku
|
|
||||||
FROM pharmacy_lpo_items i
|
|
||||||
LEFT JOIN drugs d ON i.drug_id = d.id
|
|
||||||
WHERE i.lpo_id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
|
|
||||||
} elseif ($action === 'get_suppliers') {
|
|
||||||
$stmt = $pdo->query("SELECT id, name_en, name_ar FROM suppliers ORDER BY name_en ASC");
|
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
|
|
||||||
} elseif ($action === 'get_drugs') {
|
|
||||||
$stmt = $pdo->query("SELECT id, name_en, name_ar, sku, price FROM drugs ORDER BY name_en ASC");
|
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
|
|
||||||
} elseif ($action === 'get_returns') {
|
|
||||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
|
||||||
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20;
|
|
||||||
$offset = ($page - 1) * $limit;
|
|
||||||
|
|
||||||
$countStmt = $pdo->query("SELECT COUNT(*) FROM pharmacy_purchase_returns");
|
|
||||||
$total = $countStmt->fetchColumn();
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare("
|
|
||||||
SELECT r.*, s.name_en as supplier_name
|
|
||||||
FROM pharmacy_purchase_returns r
|
|
||||||
LEFT JOIN suppliers s ON r.supplier_id = s.id
|
|
||||||
ORDER BY r.return_date DESC
|
|
||||||
LIMIT :limit OFFSET :offset
|
|
||||||
");
|
|
||||||
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
'data' => $stmt->fetchAll(PDO::FETCH_ASSOC),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'limit' => $limit,
|
|
||||||
'pages' => ceil($total / $limit)
|
|
||||||
]);
|
|
||||||
|
|
||||||
} elseif ($action === 'get_return_details') {
|
|
||||||
$id = $_GET['id'] ?? 0;
|
|
||||||
$stmt = $pdo->prepare("
|
|
||||||
SELECT i.*, d.name_en as drug_name, d.sku, b.batch_number
|
|
||||||
FROM pharmacy_purchase_return_items i
|
|
||||||
LEFT JOIN drugs d ON i.drug_id = d.id
|
|
||||||
LEFT JOIN pharmacy_batches b ON i.batch_id = b.id
|
|
||||||
WHERE i.return_id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
|
|
||||||
} elseif ($action === 'get_supplier_batches') {
|
|
||||||
// Get batches for a specific supplier (or all if not specified, but usually filtered by drug)
|
|
||||||
// Ideally: We select a supplier for return, then we select items.
|
|
||||||
// Better: Select Drug -> Show Batches (maybe filter by supplier if we track supplier_id in batch)
|
|
||||||
$drug_id = $_GET['drug_id'] ?? 0;
|
|
||||||
$supplier_id = $_GET['supplier_id'] ?? 0;
|
|
||||||
|
|
||||||
$sql = "SELECT b.id, b.batch_number, b.expiry_date, b.quantity, b.cost_price
|
|
||||||
FROM pharmacy_batches b
|
|
||||||
WHERE b.drug_id = ? AND b.quantity > 0";
|
|
||||||
$params = [$drug_id];
|
|
||||||
|
|
||||||
if ($supplier_id) {
|
|
||||||
// If we want to strictly return only what we bought from this supplier:
|
|
||||||
// $sql .= " AND b.supplier_id = ?";
|
|
||||||
// $params[] = $supplier_id;
|
|
||||||
// BUT, sometimes we might return to a supplier what we bought elsewhere if they accept it,
|
|
||||||
// or `supplier_id` in batches might be null for old data.
|
|
||||||
// Let's NOT strictly enforce supplier_id match for now, just show all batches.
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare($sql);
|
|
||||||
$stmt->execute($params);
|
|
||||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
if ($pdo->inTransaction()) {
|
|
||||||
$pdo->rollBack();
|
|
||||||
}
|
|
||||||
http_response_code(500);
|
|
||||||
echo json_encode(['error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
251
api/queue.php
251
api/queue.php
@ -1,251 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/../db/config.php';
|
|
||||||
require_once __DIR__ . '/../includes/actions.php'; // For permissions if needed
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
$action = $_GET['action'] ?? '';
|
|
||||||
$lang = $_GET['lang'] ?? 'en';
|
|
||||||
|
|
||||||
try {
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
// --- ADD TOKEN ---
|
|
||||||
if ($action === 'add') {
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
||||||
throw new Exception('Invalid request method');
|
|
||||||
}
|
|
||||||
|
|
||||||
$patient_id = $_POST['patient_id'] ?? null;
|
|
||||||
$department_id = $_POST['department_id'] ?? null;
|
|
||||||
$doctor_id = $_POST['doctor_id'] ?? null;
|
|
||||||
|
|
||||||
if (!$patient_id || !$department_id) {
|
|
||||||
throw new Exception('Patient and Department are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get next token number for this department today
|
|
||||||
$today = date('Y-m-d');
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT MAX(token_number)
|
|
||||||
FROM patient_queue
|
|
||||||
WHERE department_id = ?
|
|
||||||
AND DATE(created_at) = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$department_id, $today]);
|
|
||||||
$max_token = $stmt->fetchColumn();
|
|
||||||
$next_token = ($max_token) ? $max_token + 1 : 1;
|
|
||||||
|
|
||||||
// Insert
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
INSERT INTO patient_queue (patient_id, department_id, doctor_id, token_number, status, created_at)
|
|
||||||
VALUES (?, ?, ?, ?, 'waiting', NOW())
|
|
||||||
");
|
|
||||||
$stmt->execute([$patient_id, $department_id, $doctor_id ?: null, $next_token]);
|
|
||||||
$queue_id = $db->lastInsertId();
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Token generated', 'token_number' => $next_token, 'queue_id' => $queue_id]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- LIST QUEUE ---
|
|
||||||
if ($action === 'list') {
|
|
||||||
$dept_id = $_GET['department_id'] ?? null;
|
|
||||||
$doc_id = $_GET['doctor_id'] ?? null;
|
|
||||||
$status = $_GET['status'] ?? null; // Can be comma separated 'waiting,serving'
|
|
||||||
$today = date('Y-m-d');
|
|
||||||
|
|
||||||
$where = "WHERE DATE(q.created_at) = ?";
|
|
||||||
$params = [$today];
|
|
||||||
|
|
||||||
if ($dept_id) {
|
|
||||||
$where .= " AND q.department_id = ?";
|
|
||||||
$params[] = $dept_id;
|
|
||||||
}
|
|
||||||
if ($doc_id) {
|
|
||||||
$where .= " AND (q.doctor_id = ? OR q.doctor_id IS NULL)";
|
|
||||||
$params[] = $doc_id;
|
|
||||||
}
|
|
||||||
if ($status) {
|
|
||||||
$statuses = explode(',', $status);
|
|
||||||
$placeholders = implode(',', array_fill(0, count($statuses), '?'));
|
|
||||||
$where .= " AND q.status IN ($placeholders)";
|
|
||||||
$params = array_merge($params, $statuses);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql = "
|
|
||||||
SELECT q.*, td.name_$lang as target_department_name,
|
|
||||||
p.name as patient_name,
|
|
||||||
d.name_$lang as doctor_name,
|
|
||||||
d.name_en as doctor_name_en,
|
|
||||||
d.name_ar as doctor_name_ar,
|
|
||||||
d.room_number,
|
|
||||||
dept.name_$lang as department_name,
|
|
||||||
dept.name_en as department_name_en,
|
|
||||||
dept.name_ar as department_name_ar
|
|
||||||
FROM patient_queue q
|
|
||||||
JOIN patients p ON q.patient_id = p.id
|
|
||||||
JOIN departments dept ON q.department_id = dept.id
|
|
||||||
LEFT JOIN departments td ON q.target_department_id = td.id
|
|
||||||
LEFT JOIN employees d ON q.doctor_id = d.id
|
|
||||||
$where
|
|
||||||
ORDER BY
|
|
||||||
CASE WHEN q.status = 'serving' THEN 1 WHEN q.status = 'waiting' THEN 2 ELSE 3 END,
|
|
||||||
q.token_number ASC
|
|
||||||
";
|
|
||||||
|
|
||||||
$stmt = $db->prepare($sql);
|
|
||||||
$stmt->execute($params);
|
|
||||||
$queue = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'data' => $queue]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- UPDATE STATUS ---
|
|
||||||
if ($action === 'update_status') {
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
||||||
throw new Exception('Invalid request method');
|
|
||||||
}
|
|
||||||
|
|
||||||
$queue_id = $_POST['queue_id'] ?? null;
|
|
||||||
$new_status = $_POST['status'] ?? null;
|
|
||||||
$doctor_id = $_POST['doctor_id'] ?? null; // If a doctor picks up a general department token
|
|
||||||
|
|
||||||
if (!$queue_id || !$new_status) {
|
|
||||||
throw new Exception('Queue ID and Status are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_array($new_status, ['waiting', 'serving', 'completed', 'cancelled'])) {
|
|
||||||
throw new Exception('Invalid status');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logic: If setting to 'serving', update doctor_id if provided
|
|
||||||
$sql = "UPDATE patient_queue SET status = ?, updated_at = NOW()";
|
|
||||||
$params = [$new_status];
|
|
||||||
|
|
||||||
if ($new_status === 'serving' && $doctor_id) {
|
|
||||||
$sql .= ", doctor_id = ?";
|
|
||||||
$params[] = $doctor_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql .= " WHERE id = ?";
|
|
||||||
$params[] = $queue_id;
|
|
||||||
|
|
||||||
$stmt = $db->prepare($sql);
|
|
||||||
$stmt->execute($params);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Status updated']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// --- TRANSFER TOKEN ---
|
|
||||||
if ($action === 'transfer') {
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
||||||
throw new Exception('Invalid request method');
|
|
||||||
}
|
|
||||||
|
|
||||||
$queue_id = $_POST['queue_id'] ?? null;
|
|
||||||
$new_department_id = $_POST['department_id'] ?? null;
|
|
||||||
$new_doctor_id = $_POST['doctor_id'] ?? null;
|
|
||||||
|
|
||||||
if (!$queue_id || !$new_department_id) {
|
|
||||||
throw new Exception('Queue ID and Target Department are required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current queue token
|
|
||||||
$stmt = $db->prepare("SELECT patient_id FROM patient_queue WHERE id = ?");
|
|
||||||
$stmt->execute([$queue_id]);
|
|
||||||
$current = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if (!$current) {
|
|
||||||
throw new Exception('Queue token not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete the old token
|
|
||||||
$stmt = $db->prepare("UPDATE patient_queue SET status = 'completed', updated_at = NOW() WHERE id = ?");
|
|
||||||
$stmt->execute([$queue_id]);
|
|
||||||
|
|
||||||
// Create new token
|
|
||||||
$today = date('Y-m-d');
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
SELECT MAX(token_number)
|
|
||||||
FROM patient_queue
|
|
||||||
WHERE department_id = ?
|
|
||||||
AND DATE(created_at) = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$new_department_id, $today]);
|
|
||||||
$max_token = $stmt->fetchColumn();
|
|
||||||
$next_token = ($max_token) ? $max_token + 1 : 1;
|
|
||||||
|
|
||||||
$stmt = $db->prepare("
|
|
||||||
INSERT INTO patient_queue (patient_id, department_id, doctor_id, token_number, status, created_at)
|
|
||||||
VALUES (?, ?, ?, ?, 'waiting', NOW())
|
|
||||||
");
|
|
||||||
$stmt->execute([$current['patient_id'], $new_department_id, $new_doctor_id ?: null, $next_token]);
|
|
||||||
$new_queue_id = $db->lastInsertId();
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Token transferred', 'token_number' => $next_token, 'new_queue_id' => $new_queue_id]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- SUMMARY ---
|
|
||||||
if ($action === 'summary') {
|
|
||||||
$today = date('Y-m-d');
|
|
||||||
$dept_id = $_GET['department_id'] ?? null;
|
|
||||||
|
|
||||||
$where = "WHERE DATE(q.created_at) = ?";
|
|
||||||
$params = [$today];
|
|
||||||
|
|
||||||
if ($dept_id) {
|
|
||||||
$where .= " AND q.department_id = ?";
|
|
||||||
$params[] = $dept_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql = "
|
|
||||||
SELECT
|
|
||||||
dept.name_$lang as department_name,
|
|
||||||
dept.id as department_id,
|
|
||||||
SUM(CASE WHEN q.status = 'waiting' THEN 1 ELSE 0 END) as waiting,
|
|
||||||
SUM(CASE WHEN q.status = 'serving' THEN 1 ELSE 0 END) as serving,
|
|
||||||
SUM(CASE WHEN q.status = 'completed' THEN 1 ELSE 0 END) as completed
|
|
||||||
FROM patient_queue q
|
|
||||||
JOIN departments dept ON q.department_id = dept.id
|
|
||||||
LEFT JOIN departments td ON q.target_department_id = td.id
|
|
||||||
$where
|
|
||||||
GROUP BY dept.id
|
|
||||||
";
|
|
||||||
|
|
||||||
$stmt = $db->prepare($sql);
|
|
||||||
$stmt->execute($params);
|
|
||||||
$summary = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'data' => $summary]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- GET ADS ---
|
|
||||||
if ($action === 'get_ads') {
|
|
||||||
$stmt = $db->query("SELECT * FROM queue_ads WHERE active = 1 ORDER BY created_at DESC");
|
|
||||||
$ads = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
// Return both languages
|
|
||||||
$data = array_map(function($ad) {
|
|
||||||
return [
|
|
||||||
'id' => $ad['id'],
|
|
||||||
'text_en' => $ad['text_en'],
|
|
||||||
'text_ar' => $ad['text_ar']
|
|
||||||
];
|
|
||||||
}, $ads);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'data' => $data]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception('Invalid action');
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(400);
|
|
||||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$db = db();
|
|
||||||
// Ensure buffered query is on if possible (though config might override)
|
|
||||||
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
|
|
||||||
|
|
||||||
$db->query("SET FOREIGN_KEY_CHECKS=0;");
|
|
||||||
$files = glob('db/migrations/*.sql');
|
|
||||||
sort($files);
|
|
||||||
|
|
||||||
foreach ($files as $file) {
|
|
||||||
echo "Processing $file...\n";
|
|
||||||
$sql_content = file_get_contents($file);
|
|
||||||
$sql_content = preg_replace('/--.*$/m', '', $sql_content);
|
|
||||||
$statements = explode(';', $sql_content);
|
|
||||||
|
|
||||||
foreach ($statements as $sql) {
|
|
||||||
$sql = trim($sql);
|
|
||||||
if (empty($sql)) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Use query() instead of exec() to handle potential result sets (like SELECT 1)
|
|
||||||
// and close the cursor explicitly.
|
|
||||||
$stmt = $db->query($sql);
|
|
||||||
if ($stmt) {
|
|
||||||
$stmt->closeCursor();
|
|
||||||
}
|
|
||||||
echo "Executed: " . substr(str_replace("\n", " ", $sql), 0, 60) . "...\n";
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$msg = $e->getMessage();
|
|
||||||
if (strpos($msg, "Duplicate column") !== false ||
|
|
||||||
strpos($msg, "already exists") !== false ||
|
|
||||||
strpos($msg, "Duplicate key") !== false ||
|
|
||||||
strpos($msg, "1062 Duplicate entry") !== false ||
|
|
||||||
strpos($msg, "1054 Unknown column") !== false ||
|
|
||||||
strpos($msg, "1146 Table") !== false ||
|
|
||||||
strpos($msg, "1553 Cannot drop index") !== false || strpos($msg, "1826 Duplicate FOREIGN KEY") !== false || strpos($msg, "1828 Cannot drop column") !== false) {
|
|
||||||
echo "Skipped (Exists): " . substr(str_replace("\n", " ", $sql), 0, 60) . "...\n";
|
|
||||||
} else {
|
|
||||||
echo "Error: " . $msg . "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$db->query("SET FOREIGN_KEY_CHECKS=1;");
|
|
||||||
echo "All migrations applied.\n";
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
$section = 'appointments';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
$db = db();
|
|
||||||
$lang = $_SESSION['lang'] ?? 'en';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
require_once __DIR__ . '/includes/pages/appointments.php';
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 236 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 246 KiB |
@ -1,83 +0,0 @@
|
|||||||
async function generateAISuggestion(button) {
|
|
||||||
const target = button.dataset.target || 'treatment_plan';
|
|
||||||
const modal = button.closest(".modal");
|
|
||||||
|
|
||||||
// Get editor instances or DOM elements
|
|
||||||
const symptomsEditor = $(modal.querySelector('textarea[name="symptoms"]'));
|
|
||||||
const diagnosisEditor = $(modal.querySelector('textarea[name="diagnosis"]'));
|
|
||||||
const treatmentPlanEditor = $(modal.querySelector('textarea[name="treatment_plan"]'));
|
|
||||||
|
|
||||||
// Helper to get value from Summernote or textarea
|
|
||||||
const getValue = (editor, selector) => {
|
|
||||||
if (editor.length && editor.summernote) return editor.summernote('code');
|
|
||||||
const el = modal.querySelector(selector);
|
|
||||||
return el ? el.value : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const symptomsText = getValue(symptomsEditor, 'textarea[name="symptoms"]');
|
|
||||||
const diagnosisText = getValue(diagnosisEditor, 'textarea[name="diagnosis"]');
|
|
||||||
|
|
||||||
// Helper to set value
|
|
||||||
const setValue = (editor, selector, val) => {
|
|
||||||
if (editor.length && editor.summernote) {
|
|
||||||
editor.summernote('code', val);
|
|
||||||
} else {
|
|
||||||
const el = modal.querySelector(selector);
|
|
||||||
if (el) el.value = val;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
const cleanSymptoms = symptomsText.replace(/<[^>]*>/g, "").trim();
|
|
||||||
const cleanDiagnosis = diagnosisText.replace(/<[^>]*>/g, "").trim();
|
|
||||||
|
|
||||||
if (target === 'diagnosis' && !cleanSymptoms) {
|
|
||||||
alert("Please enter symptoms first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (target === 'treatment_plan' && (!cleanSymptoms && !cleanDiagnosis)) {
|
|
||||||
alert("Please enter symptoms or diagnosis first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const originalHTML = button.innerHTML;
|
|
||||||
button.disabled = true;
|
|
||||||
button.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> AI...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const payload = {
|
|
||||||
target: target,
|
|
||||||
symptoms: symptomsText,
|
|
||||||
diagnosis: diagnosisText,
|
|
||||||
current_value: (target === 'symptoms' ? symptomsText : (target === 'diagnosis' ? diagnosisText : ''))
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch("api/ai_report.php", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
if (target === 'symptoms') {
|
|
||||||
setValue(symptomsEditor, 'textarea[name="symptoms"]', data.report);
|
|
||||||
} else if (target === 'diagnosis') {
|
|
||||||
setValue(diagnosisEditor, 'textarea[name="diagnosis"]', data.report);
|
|
||||||
} else {
|
|
||||||
setValue(treatmentPlanEditor, 'textarea[name="treatment_plan"]', data.report);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
alert("AI Error: " + (data.error || "Unknown error"));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("AI Suggestion failed:", error);
|
|
||||||
alert("Failed to generate AI suggestion.");
|
|
||||||
} finally {
|
|
||||||
button.disabled = false;
|
|
||||||
button.innerHTML = originalHTML;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alias for backward compatibility
|
|
||||||
window.generateAIReport = generateAISuggestion;
|
|
||||||
@ -1,67 +1,39 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// --- Chat Widget Logic ---
|
|
||||||
const chatForm = document.getElementById('chat-form');
|
const chatForm = document.getElementById('chat-form');
|
||||||
const chatInput = document.getElementById('chat-input');
|
const chatInput = document.getElementById('chat-input');
|
||||||
const chatMessages = document.getElementById('chat-messages');
|
const chatMessages = document.getElementById('chat-messages');
|
||||||
|
|
||||||
if (chatForm && chatInput && chatMessages) {
|
const appendMessage = (text, sender) => {
|
||||||
const appendMessage = (text, sender) => {
|
const msgDiv = document.createElement('div');
|
||||||
const msgDiv = document.createElement('div');
|
msgDiv.classList.add('message', sender);
|
||||||
msgDiv.classList.add('message', sender);
|
msgDiv.textContent = text;
|
||||||
msgDiv.textContent = text;
|
chatMessages.appendChild(msgDiv);
|
||||||
chatMessages.appendChild(msgDiv);
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
chatForm.addEventListener('submit', async (e) => {
|
chatForm.addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const message = chatInput.value.trim();
|
const message = chatInput.value.trim();
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
|
||||||
appendMessage(message, 'visitor');
|
appendMessage(message, 'visitor');
|
||||||
chatInput.value = '';
|
chatInput.value = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('api/chat.php', {
|
const response = await fetch('api/chat.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ message })
|
body: JSON.stringify({ message })
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Artificial delay for realism
|
// Artificial delay for realism
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
appendMessage(data.reply, 'bot');
|
appendMessage(data.reply, 'bot');
|
||||||
}, 500);
|
}, 500);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
|
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// --- Patient Form: Auto-calculate DOB from Age ---
|
|
||||||
// Use jQuery for better compatibility with Inputmask and existing events
|
|
||||||
function setupAgeToDob(ageId, dobId) {
|
|
||||||
$(document).on('input', '#' + ageId, function() {
|
|
||||||
var age = parseInt($(this).val());
|
|
||||||
var $dob = $('#' + dobId);
|
|
||||||
|
|
||||||
if (!isNaN(age) && age >= 0) {
|
|
||||||
var currentYear = new Date().getFullYear();
|
|
||||||
var birthYear = currentYear - age;
|
|
||||||
// Default to Jan 1st of the birth year: YYYY-01-01
|
|
||||||
var dob = birthYear + '-01-01';
|
|
||||||
|
|
||||||
// Set value and trigger input/change for Inputmask and other listeners
|
|
||||||
$dob.val(dob).trigger('input').trigger('change');
|
|
||||||
} else {
|
|
||||||
// Optional: Clear DOB if age is invalid/cleared?
|
|
||||||
// $dob.val('').trigger('input');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setupAgeToDob('add_patient_age', 'add_patient_dob');
|
|
||||||
setupAgeToDob('edit_patient_age', 'edit_patient_dob');
|
|
||||||
});
|
});
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 246 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
16
billing.php
16
billing.php
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
$section = 'billing';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
|
|
||||||
$db = db();
|
|
||||||
$lang = $_SESSION['lang'];
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
require_once __DIR__ . '/includes/pages/billing.php';
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$db = db();
|
|
||||||
$stmt = $db->query("DESCRIBE appointments");
|
|
||||||
$columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
||||||
print_r($columns);
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
// Fetch one patient
|
|
||||||
$patient = $db->query("SELECT id, name FROM patients LIMIT 1")->fetch(PDO::FETCH_ASSOC);
|
|
||||||
// Fetch one doctor (employee with position 'Doctor')
|
|
||||||
$doctor = $db->query("
|
|
||||||
SELECT e.id, e.name_en
|
|
||||||
FROM employees e
|
|
||||||
JOIN positions p ON e.position_id = p.id
|
|
||||||
WHERE UPPER(p.name_en) = 'DOCTOR'
|
|
||||||
LIMIT 1")->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($patient && $doctor) {
|
|
||||||
echo "Found Patient: " . $patient['name'] . " (ID: " . $patient['id'] . ")\n";
|
|
||||||
echo "Found Doctor: " . $doctor['name_en'] . " (ID: " . $doctor['id'] . ")\n";
|
|
||||||
} else {
|
|
||||||
echo "Could not find patient or doctor.\n";
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
$tables = ['xray_inquiry_items', 'inquiry_tests', 'visit_prescriptions', 'laboratory_inquiries'];
|
|
||||||
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
try {
|
|
||||||
$stmt = $db->query("SHOW CREATE TABLE $table");
|
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
echo $row['Create Table'] . "\n\n";
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo "Table $table not found: " . $e->getMessage() . "\n\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$result = $db->query("SHOW COLUMNS FROM doctor_holidays");
|
|
||||||
if ($result) {
|
|
||||||
$columns = $result->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
echo "Table 'doctor_holidays' exists with columns:\n";
|
|
||||||
foreach ($columns as $col) {
|
|
||||||
echo "- " . $col['Field'] . " (" . $col['Type'] . ")\n";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo "Table 'doctor_holidays' does not exist.\n";
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo "Error: " . $e->getMessage() . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$db = db();
|
|
||||||
$stmt = $db->query("DESCRIBE employees");
|
|
||||||
$columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
||||||
echo "Columns in employees table:\n";
|
|
||||||
print_r($columns);
|
|
||||||
?>
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$db = db();
|
|
||||||
$stmt = $db->query("DESCRIBE nurses");
|
|
||||||
$columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
||||||
print_r($columns);
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$pdo = db();
|
|
||||||
|
|
||||||
$tables = ['drugs', 'suppliers', 'visit_prescriptions'];
|
|
||||||
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
echo "--- Table: $table ---
|
|
||||||
";
|
|
||||||
try {
|
|
||||||
$stmt = $pdo->query("DESCRIBE $table");
|
|
||||||
$columns = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
foreach ($columns as $col) {
|
|
||||||
echo "{$col['Field']} ({$col['Type']})
|
|
||||||
";
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo "Error describing $table: " . $e->getMessage() . "
|
|
||||||
";
|
|
||||||
}
|
|
||||||
echo "
|
|
||||||
";
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
$tables = ['laboratory_tests', 'xray_tests', 'drugs'];
|
|
||||||
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
try {
|
|
||||||
$stmt = $db->query("SHOW CREATE TABLE $table");
|
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
echo $row['Create Table'] . "\n\n";
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo "Table $table not found or error: " . $e->getMessage() . "\n\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
$tables = ['services', 'xray_inquiries', 'laboratory_inquiries', 'visit_prescriptions', 'bill_items'];
|
|
||||||
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
try {
|
|
||||||
$stmt = $db->query("SHOW CREATE TABLE $table");
|
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
echo $row['Create Table'] . "\n\n";
|
|
||||||
} catch (Exception $e) {
|
|
||||||
echo "Table $table not found or error: " . $e->getMessage() . "\n\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
16
cities.php
16
cities.php
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
$section = 'cities';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
|
|
||||||
$db = db();
|
|
||||||
$lang = $_SESSION['lang'] ?? 'en';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
require_once __DIR__ . '/includes/pages/cities.php';
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Enable detailed error reporting for debugging 500 errors
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
ini_set('display_startup_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
$section = 'dashboard';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
|
|
||||||
// Try to connect to DB first to catch connection errors early
|
|
||||||
try {
|
|
||||||
$db = db();
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
die("Database Connection Error: " . $e->getMessage());
|
|
||||||
} catch (Exception $e) {
|
|
||||||
die("General Error: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now include helpers, which can use the existing $db connection
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
|
|
||||||
// Auth Check
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
|
|
||||||
// $db is already set above, so no need to call db() again, but it's safe if we do.
|
|
||||||
// $db = db();
|
|
||||||
|
|
||||||
$lang = $_SESSION['lang'];
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
|
|
||||||
if (!isset($_GET['ajax_search'])) {
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/pages/dashboard.php';
|
|
||||||
|
|
||||||
if (!isset($_GET['ajax_search'])) {
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
}
|
|
||||||
@ -1,20 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
// Generated by setup_mariadb_project.sh — edit as needed.
|
// Generated by setup_mariadb_project.sh — edit as needed.
|
||||||
if (!defined('DB_HOST')) define('DB_HOST', getenv('DB_HOST') ?: '127.0.0.1');
|
define('DB_HOST', '127.0.0.1');
|
||||||
if (!defined('DB_NAME')) define('DB_NAME', getenv('DB_NAME') ?: 'app_38960');
|
define('DB_NAME', 'app_38960');
|
||||||
if (!defined('DB_USER')) define('DB_USER', getenv('DB_USER') ?: 'app_38960');
|
define('DB_USER', 'app_38960');
|
||||||
if (!defined('DB_PASS')) define('DB_PASS', getenv('DB_PASS') ?: '36fb441e-8408-4101-afdc-7911dc065e36');
|
define('DB_PASS', '36fb441e-8408-4101-afdc-7911dc065e36');
|
||||||
|
|
||||||
if (!function_exists('db')) {
|
function db() {
|
||||||
function db() {
|
static $pdo;
|
||||||
static $pdo;
|
if (!$pdo) {
|
||||||
if (!$pdo) {
|
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
]);
|
||||||
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
}
|
||||||
]);
|
return $pdo;
|
||||||
}
|
|
||||||
return $pdo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
-- Migration: Add attachment to inquiry_tests
|
|
||||||
-- This table might have been created by previous agents or manually
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS inquiry_tests (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
inquiry_id INT,
|
|
||||||
test_id INT,
|
|
||||||
result VARCHAR(255),
|
|
||||||
normal_range VARCHAR(255),
|
|
||||||
attachment VARCHAR(255),
|
|
||||||
FOREIGN KEY (inquiry_id) REFERENCES laboratory_inquiries(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (test_id) REFERENCES laboratory_tests(id) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- Ensure attachment column exists (in case inquiry_tests already existed without it)
|
|
||||||
SET @dbname = DATABASE();
|
|
||||||
SET @tablename = "inquiry_tests";
|
|
||||||
SET @columnname = "attachment";
|
|
||||||
SET @preparedStatement = (SELECT IF(
|
|
||||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = @dbname
|
|
||||||
AND TABLE_NAME = @tablename
|
|
||||||
AND COLUMN_NAME = @columnname
|
|
||||||
) > 0,
|
|
||||||
"SELECT 1",
|
|
||||||
"ALTER TABLE inquiry_tests ADD COLUMN attachment VARCHAR(255) AFTER normal_range"
|
|
||||||
));
|
|
||||||
PREPARE stmt FROM @preparedStatement;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
-- Create appointments table
|
|
||||||
CREATE TABLE IF NOT EXISTS appointments (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
patient_id INT NOT NULL,
|
|
||||||
doctor_id INT NOT NULL,
|
|
||||||
start_time DATETIME NOT NULL,
|
|
||||||
end_time DATETIME NOT NULL,
|
|
||||||
status ENUM('Scheduled', 'Completed', 'Cancelled') DEFAULT 'Scheduled',
|
|
||||||
reason TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (doctor_id) REFERENCES doctors(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create holidays table
|
|
||||||
CREATE TABLE IF NOT EXISTS holidays (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
holiday_date DATE NOT NULL,
|
|
||||||
name_en VARCHAR(255) NOT NULL,
|
|
||||||
name_ar VARCHAR(255) NOT NULL,
|
|
||||||
is_recurring BOOLEAN DEFAULT FALSE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create doctor schedules table (working hours)
|
|
||||||
CREATE TABLE IF NOT EXISTS doctor_schedules (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
doctor_id INT NOT NULL,
|
|
||||||
day_of_week INT NOT NULL, -- 0 (Sunday) to 6 (Saturday)
|
|
||||||
start_time TIME NOT NULL,
|
|
||||||
end_time TIME NOT NULL,
|
|
||||||
FOREIGN KEY (doctor_id) REFERENCES doctors(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Seed some holidays
|
|
||||||
INSERT INTO holidays (holiday_date, name_en, name_ar, is_recurring) VALUES
|
|
||||||
('2026-01-01', 'New Year', 'رأس السنة', TRUE),
|
|
||||||
('2026-12-25', 'Christmas', 'عيد الميلاد', TRUE);
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS settings (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
setting_key VARCHAR(100) NOT NULL UNIQUE,
|
|
||||||
setting_value TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO settings (setting_key, setting_value) VALUES
|
|
||||||
('company_name', 'Hospital Management System'),
|
|
||||||
('company_logo', ''),
|
|
||||||
('company_favicon', ''),
|
|
||||||
('company_ctr_no', ''),
|
|
||||||
('company_registration_no', ''),
|
|
||||||
('company_address', ''),
|
|
||||||
('company_phone', ''),
|
|
||||||
('company_email', ''),
|
|
||||||
('company_vat_no', '');
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
|
|
||||||
-- X-Ray Module Tables
|
|
||||||
CREATE TABLE IF NOT EXISTS xray_groups (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name_en VARCHAR(255) NOT NULL,
|
|
||||||
name_ar VARCHAR(255) NOT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS xray_tests (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
group_id INT,
|
|
||||||
name_en VARCHAR(255) NOT NULL,
|
|
||||||
name_ar VARCHAR(255) NOT NULL,
|
|
||||||
price DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (group_id) REFERENCES xray_groups(id) ON DELETE SET NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS xray_inquiries (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
patient_name VARCHAR(255) NOT NULL,
|
|
||||||
inquiry_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
source ENUM('Internal', 'External') DEFAULT 'Internal',
|
|
||||||
status ENUM('Pending', 'Completed', 'Cancelled') DEFAULT 'Pending',
|
|
||||||
notes TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS xray_inquiry_items (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
inquiry_id INT NOT NULL,
|
|
||||||
xray_id INT NOT NULL,
|
|
||||||
result TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (inquiry_id) REFERENCES xray_inquiries(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (xray_id) REFERENCES xray_tests(id) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
-- Update laboratory_inquiries and xray_inquiries to link with patients and visits
|
|
||||||
ALTER TABLE laboratory_inquiries ADD COLUMN patient_id INT NULL AFTER id;
|
|
||||||
ALTER TABLE laboratory_inquiries ADD COLUMN visit_id INT NULL AFTER patient_id;
|
|
||||||
ALTER TABLE laboratory_inquiries MODIFY patient_name VARCHAR(255) NULL;
|
|
||||||
|
|
||||||
ALTER TABLE laboratory_inquiries ADD CONSTRAINT fk_lab_patient FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE;
|
|
||||||
ALTER TABLE laboratory_inquiries ADD CONSTRAINT fk_lab_visit FOREIGN KEY (visit_id) REFERENCES visits(id) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
ALTER TABLE xray_inquiries ADD COLUMN patient_id INT NULL AFTER id;
|
|
||||||
ALTER TABLE xray_inquiries ADD COLUMN visit_id INT NULL AFTER patient_id;
|
|
||||||
ALTER TABLE xray_inquiries MODIFY patient_name VARCHAR(255) NULL;
|
|
||||||
|
|
||||||
ALTER TABLE xray_inquiries ADD CONSTRAINT fk_xray_patient FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE;
|
|
||||||
ALTER TABLE xray_inquiries ADD CONSTRAINT fk_xray_visit FOREIGN KEY (visit_id) REFERENCES visits(id) ON DELETE SET NULL;
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
|
|
||||||
-- Seed X-Ray Groups
|
|
||||||
INSERT INTO xray_groups (name_en, name_ar) VALUES ('Chest', 'الصدر');
|
|
||||||
SET @chest_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO xray_groups (name_en, name_ar) VALUES ('Extremities', 'الأطراف');
|
|
||||||
SET @ext_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO xray_groups (name_en, name_ar) VALUES ('Spine', 'العمود الفقري');
|
|
||||||
SET @spine_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO xray_groups (name_en, name_ar) VALUES ('Abdomen', 'البطن');
|
|
||||||
SET @abd_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO xray_groups (name_en, name_ar) VALUES ('Dental', 'الأسنان');
|
|
||||||
SET @den_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO xray_groups (name_en, name_ar) VALUES ('Skull', 'الجمجمة');
|
|
||||||
SET @skl_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
-- Seed X-Ray Tests
|
|
||||||
-- Chest
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@chest_id, 'Chest PA/Lateral', 'أشعة على الصدر - وضع أمامي جانبي', 150.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@chest_id, 'Chest PA Only', 'أشعة على الصدر - وضع أمامي خلفي فقط', 100.00);
|
|
||||||
|
|
||||||
-- Extremities
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@ext_id, 'Hand AP/Lateral', 'أشعة على اليد', 120.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@ext_id, 'Foot AP/Lateral', 'أشعة على القدم', 120.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@ext_id, 'Knee AP/Lateral', 'أشعة على الركبة', 140.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@ext_id, 'Shoulder AP/Lateral', 'أشعة على الكتف', 150.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@ext_id, 'Elbow AP/Lateral', 'أشعة على الكوع', 120.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@ext_id, 'Ankle AP/Lateral', 'أشعة على الكاحل', 120.00);
|
|
||||||
|
|
||||||
-- Spine
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@spine_id, 'Cervical Spine AP/Lateral', 'أشعة على الفقرات العنقية', 180.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@spine_id, 'Lumbar Spine AP/Lateral', 'أشعة على الفقرات القطنية', 200.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@spine_id, 'Thoracic Spine AP/Lateral', 'أشعة على الفقرات الصدرية', 180.00);
|
|
||||||
|
|
||||||
-- Abdomen
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@abd_id, 'Abdomen KUB', 'أشعة على البطن والمسالك البولية', 160.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@abd_id, 'Abdomen Erect/Supine', 'أشعة على البطن', 160.00);
|
|
||||||
|
|
||||||
-- Dental
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@den_id, 'Dental Panoramic', 'أشعة بانوراما للأسنان', 250.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@den_id, 'Periapical X-Ray', 'أشعة صغيرة للأسنان', 50.00);
|
|
||||||
|
|
||||||
-- Skull
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@skl_id, 'Skull AP/Lateral', 'أشعة على الجمجمة', 180.00);
|
|
||||||
INSERT INTO xray_tests (group_id, name_en, name_ar, price) VALUES (@skl_id, 'Paranasal Sinuses', 'أشعة على الجيوب الأنفية', 150.00);
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
-- Add attachment column to xray_inquiry_items to store uploaded images/results
|
|
||||||
ALTER TABLE xray_inquiry_items ADD COLUMN attachment VARCHAR(255) DEFAULT NULL;
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
-- Add civil_id, nationality, city to patients
|
|
||||||
ALTER TABLE patients
|
|
||||||
ADD COLUMN civil_id VARCHAR(50) DEFAULT NULL,
|
|
||||||
ADD COLUMN nationality VARCHAR(100) DEFAULT NULL,
|
|
||||||
ADD COLUMN city VARCHAR(100) DEFAULT NULL;
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS drugs_groups (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name_en VARCHAR(255) NOT NULL,
|
|
||||||
name_ar VARCHAR(255) NOT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS drugs (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
group_id INT,
|
|
||||||
name_en VARCHAR(255) NOT NULL,
|
|
||||||
name_ar VARCHAR(255) NOT NULL,
|
|
||||||
description_en TEXT,
|
|
||||||
description_ar TEXT,
|
|
||||||
default_dosage VARCHAR(255),
|
|
||||||
default_instructions TEXT,
|
|
||||||
price DECIMAL(10, 2) DEFAULT 0.00,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (group_id) REFERENCES drugs_groups(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS suppliers (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name_en VARCHAR(255) NOT NULL,
|
|
||||||
name_ar VARCHAR(255) NOT NULL,
|
|
||||||
contact_person VARCHAR(255),
|
|
||||||
phone VARCHAR(50),
|
|
||||||
email VARCHAR(100),
|
|
||||||
address TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE drugs ADD COLUMN expiry_date DATE DEFAULT NULL;
|
|
||||||
ALTER TABLE drugs ADD COLUMN supplier_id INT DEFAULT NULL;
|
|
||||||
ALTER TABLE drugs ADD CONSTRAINT fk_drugs_supplier FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL;
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS visit_prescriptions (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
visit_id INT NOT NULL,
|
|
||||||
drug_name VARCHAR(255) NOT NULL,
|
|
||||||
dosage VARCHAR(100),
|
|
||||||
instructions TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (visit_id) REFERENCES visits(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE drugs MODIFY name_en TEXT;
|
|
||||||
ALTER TABLE drugs MODIFY name_ar TEXT;
|
|
||||||
ALTER TABLE drugs_groups MODIFY name_en TEXT;
|
|
||||||
ALTER TABLE drugs_groups MODIFY name_ar TEXT;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE employees ADD COLUMN IF NOT EXISTS position_id INT NULL;
|
|
||||||
ALTER TABLE employees DROP COLUMN IF EXISTS passion_en;
|
|
||||||
ALTER TABLE employees DROP COLUMN IF EXISTS passion_ar;
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS cities (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name_en VARCHAR(100) NOT NULL,
|
|
||||||
name_ar VARCHAR(100) NOT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
INSERT INTO cities (name_en, name_ar) VALUES
|
|
||||||
('Muscat', 'مسقط'),
|
|
||||||
('Salalah', 'صلالة'),
|
|
||||||
('Sohar', 'صحار'),
|
|
||||||
('Nizwa', 'نزوى'),
|
|
||||||
('Sur', 'صور'),
|
|
||||||
('Al Buraimi', 'البريمي'),
|
|
||||||
('Seeb', 'السيب'),
|
|
||||||
('Bawshar', 'بوشر'),
|
|
||||||
('Ibri', 'عبري'),
|
|
||||||
('Rustaq', 'الرستاق'),
|
|
||||||
('Khasab', 'خصب'),
|
|
||||||
('Bahla', 'بهلاء');
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS services (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name_en VARCHAR(255) NOT NULL,
|
|
||||||
name_ar VARCHAR(255) NOT NULL,
|
|
||||||
department_id INT NOT NULL,
|
|
||||||
price DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
|
||||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE CASCADE
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
SET @dbname = DATABASE();
|
|
||||||
SET @tablename = "poisons";
|
|
||||||
SET @targetname = "positions";
|
|
||||||
|
|
||||||
SET @exists = (
|
|
||||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
|
|
||||||
WHERE table_schema = @dbname
|
|
||||||
AND table_name = @tablename
|
|
||||||
);
|
|
||||||
|
|
||||||
SET @target_exists = (
|
|
||||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
|
|
||||||
WHERE table_schema = @dbname
|
|
||||||
AND table_name = @targetname
|
|
||||||
);
|
|
||||||
|
|
||||||
SET @stmt = IF(@exists > 0 AND @target_exists = 0,
|
|
||||||
CONCAT('RENAME TABLE ', @tablename, ' TO ', @targetname),
|
|
||||||
'SELECT 1'
|
|
||||||
);
|
|
||||||
|
|
||||||
PREPARE stmt FROM @stmt;
|
|
||||||
EXECUTE stmt;
|
|
||||||
DEALLOCATE PREPARE stmt;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE xray_inquiry_items MODIFY COLUMN attachment LONGTEXT;
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS doctor_holidays (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
doctor_id INT NOT NULL,
|
|
||||||
start_date DATE NOT NULL,
|
|
||||||
end_date DATE NOT NULL,
|
|
||||||
note VARCHAR(255) DEFAULT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (doctor_id) REFERENCES doctors(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
-- Create patient_queue table
|
|
||||||
CREATE TABLE IF NOT EXISTS patient_queue (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
patient_id INT NOT NULL,
|
|
||||||
department_id INT NOT NULL,
|
|
||||||
doctor_id INT NULL,
|
|
||||||
visit_id INT NULL,
|
|
||||||
token_number INT NOT NULL,
|
|
||||||
status ENUM('waiting', 'serving', 'completed', 'cancelled') DEFAULT 'waiting',
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (doctor_id) REFERENCES doctors(id) ON DELETE SET NULL,
|
|
||||||
FOREIGN KEY (visit_id) REFERENCES visits(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Index for faster searching of today's queue
|
|
||||||
CREATE INDEX idx_queue_date_dept ON patient_queue(created_at, department_id);
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE departments ADD COLUMN show_in_queue BOOLEAN DEFAULT 1;
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS queue_ads (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
text_en TEXT NOT NULL,
|
|
||||||
text_ar TEXT NOT NULL,
|
|
||||||
active TINYINT(1) DEFAULT 1,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
-- Add columns to appointments table
|
|
||||||
ALTER TABLE appointments ADD COLUMN IF NOT EXISTS nurse_id INT NULL;
|
|
||||||
ALTER TABLE appointments ADD COLUMN IF NOT EXISTS visit_type ENUM('Clinic', 'Home') DEFAULT 'Clinic';
|
|
||||||
ALTER TABLE appointments ADD COLUMN IF NOT EXISTS address TEXT NULL;
|
|
||||||
ALTER TABLE appointments ADD CONSTRAINT fk_appointment_nurse FOREIGN KEY (nurse_id) REFERENCES nurses(id) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
-- Add columns to visits table
|
|
||||||
ALTER TABLE visits ADD COLUMN IF NOT EXISTS nurse_id INT NULL;
|
|
||||||
ALTER TABLE visits ADD COLUMN IF NOT EXISTS visit_type ENUM('Clinic', 'Home') DEFAULT 'Clinic';
|
|
||||||
ALTER TABLE visits ADD COLUMN IF NOT EXISTS address TEXT NULL;
|
|
||||||
ALTER TABLE visits ADD CONSTRAINT fk_visit_nurse FOREIGN KEY (nurse_id) REFERENCES nurses(id) ON DELETE SET NULL;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE bills ADD COLUMN IF NOT EXISTS insurance_covered DECIMAL(10,2) DEFAULT 0.00;
|
|
||||||
ALTER TABLE bills ADD COLUMN IF NOT EXISTS patient_payable DECIMAL(10,2) DEFAULT 0.00;
|
|
||||||
ALTER TABLE bills ADD COLUMN IF NOT EXISTS total_amount DECIMAL(10,2) DEFAULT 0.00;
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE bills ADD COLUMN IF NOT EXISTS payment_method ENUM('Cash', 'Card', 'Insurance', 'Online', 'Other') DEFAULT 'Cash';
|
|
||||||
ALTER TABLE bills ADD COLUMN IF NOT EXISTS notes TEXT;
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE drugs ADD COLUMN sku VARCHAR(50) DEFAULT NULL AFTER id;
|
|
||||||
CREATE INDEX idx_drugs_sku ON drugs(sku);
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE visits ADD COLUMN IF NOT EXISTS status ENUM('CheckIn', 'In Progress', 'Completed', 'Cancelled') DEFAULT 'CheckIn';
|
|
||||||
ALTER TABLE visits ADD COLUMN IF NOT EXISTS checkout_time DATETIME NULL;
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
-- Create roles table
|
|
||||||
CREATE TABLE IF NOT EXISTS roles (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name VARCHAR(50) NOT NULL UNIQUE,
|
|
||||||
slug VARCHAR(50) NOT NULL UNIQUE,
|
|
||||||
permissions TEXT NULL, -- JSON or serialized array of permissions
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- Create users table
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
email VARCHAR(100) NOT NULL UNIQUE,
|
|
||||||
password VARCHAR(255) NOT NULL,
|
|
||||||
role_id INT NOT NULL,
|
|
||||||
active TINYINT(1) DEFAULT 1,
|
|
||||||
last_login DATETIME NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- Seed Roles
|
|
||||||
INSERT IGNORE INTO roles (name, slug, permissions) VALUES
|
|
||||||
('Administrator', 'admin', '*'),
|
|
||||||
('Doctor', 'doctor', '["dashboard", "patients", "visits", "appointments", "home_visits", "reports"]'),
|
|
||||||
('Nurse', 'nurse', '["dashboard", "patients", "visits", "queue"]'),
|
|
||||||
('Receptionist', 'receptionist', '["dashboard", "patients", "appointments", "queue", "billing"]'),
|
|
||||||
('Laboratorial', 'laboratorial', '["dashboard", "laboratory"]'),
|
|
||||||
('Radiologic', 'radiologic', '["dashboard", "xray"]');
|
|
||||||
|
|
||||||
-- Seed Default Admin User (password: admin123)
|
|
||||||
-- Using a simple hash for demonstration if PHP's password_hash is not available in SQL,
|
|
||||||
-- but ideally we should insert via PHP. For now, I will insert a placeholder and update it via PHP or assume I can use a known hash.
|
|
||||||
-- Hash for 'admin123' (bcrypt)
|
|
||||||
INSERT IGNORE INTO users (name, email, password, role_id)
|
|
||||||
SELECT 'System Admin', 'admin@hospital.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', id
|
|
||||||
FROM roles WHERE slug = 'admin' LIMIT 1;
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS insurance_payments (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
insurance_company_id INT NOT NULL,
|
|
||||||
amount DECIMAL(10, 2) NOT NULL,
|
|
||||||
payment_date DATE NOT NULL,
|
|
||||||
reference_number VARCHAR(100) DEFAULT NULL,
|
|
||||||
notes TEXT DEFAULT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (insurance_company_id) REFERENCES insurance_companies(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS inventory_categories (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
name_en VARCHAR(255) NOT NULL,
|
|
||||||
name_ar VARCHAR(255) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS inventory_items (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
category_id INT,
|
|
||||||
name_en VARCHAR(255) NOT NULL,
|
|
||||||
name_ar VARCHAR(255) NOT NULL,
|
|
||||||
description TEXT,
|
|
||||||
sku VARCHAR(100) UNIQUE,
|
|
||||||
unit VARCHAR(50) DEFAULT 'piece',
|
|
||||||
min_level INT DEFAULT 10,
|
|
||||||
reorder_level INT DEFAULT 20,
|
|
||||||
is_active BOOLEAN DEFAULT TRUE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (category_id) REFERENCES inventory_categories(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS inventory_batches (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
item_id INT NOT NULL,
|
|
||||||
batch_number VARCHAR(100),
|
|
||||||
expiry_date DATE,
|
|
||||||
quantity INT NOT NULL DEFAULT 0,
|
|
||||||
cost_price DECIMAL(10, 2) DEFAULT 0.00,
|
|
||||||
supplier_id INT,
|
|
||||||
received_date DATE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (item_id) REFERENCES inventory_items(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS inventory_transactions (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
item_id INT NOT NULL,
|
|
||||||
batch_id INT,
|
|
||||||
transaction_type ENUM('in', 'out', 'adjustment') NOT NULL,
|
|
||||||
quantity INT NOT NULL,
|
|
||||||
reference_type VARCHAR(50), -- 'purchase', 'consumption', 'manual_adjustment'
|
|
||||||
reference_id INT,
|
|
||||||
user_id INT,
|
|
||||||
transaction_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
notes TEXT,
|
|
||||||
FOREIGN KEY (item_id) REFERENCES inventory_items(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (batch_id) REFERENCES inventory_batches(id) ON DELETE SET NULL,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `pharmacy_lpos` (
|
|
||||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`supplier_id` INT NOT NULL,
|
|
||||||
`lpo_date` DATE NOT NULL,
|
|
||||||
`status` ENUM('Draft', 'Sent', 'Received', 'Cancelled') DEFAULT 'Draft',
|
|
||||||
`total_amount` DECIMAL(10, 2) DEFAULT 0.00,
|
|
||||||
`notes` TEXT NULL,
|
|
||||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (`supplier_id`) REFERENCES `suppliers`(`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `pharmacy_lpo_items` (
|
|
||||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`lpo_id` INT NOT NULL,
|
|
||||||
`drug_id` INT NOT NULL,
|
|
||||||
`quantity` INT NOT NULL,
|
|
||||||
`cost_price` DECIMAL(10, 2) NOT NULL,
|
|
||||||
`total_cost` DECIMAL(10, 2) NOT NULL,
|
|
||||||
FOREIGN KEY (`lpo_id`) REFERENCES `pharmacy_lpos`(`id`) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (`drug_id`) REFERENCES `drugs`(`id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
-- Create pharmacy_batches table
|
|
||||||
CREATE TABLE IF NOT EXISTS pharmacy_batches (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
drug_id INT NOT NULL,
|
|
||||||
batch_number VARCHAR(50) NOT NULL,
|
|
||||||
expiry_date DATE NOT NULL,
|
|
||||||
quantity INT NOT NULL DEFAULT 0,
|
|
||||||
cost_price DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
|
||||||
sale_price DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
|
||||||
supplier_id INT NULL,
|
|
||||||
received_date DATE NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (drug_id) REFERENCES drugs(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create pharmacy_sales table
|
|
||||||
CREATE TABLE IF NOT EXISTS pharmacy_sales (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
patient_id INT NULL,
|
|
||||||
visit_id INT NULL,
|
|
||||||
total_amount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
|
||||||
payment_method VARCHAR(50) DEFAULT 'cash',
|
|
||||||
status VARCHAR(20) DEFAULT 'completed', -- completed, refunded, cancelled
|
|
||||||
notes TEXT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE SET NULL,
|
|
||||||
FOREIGN KEY (visit_id) REFERENCES visits(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create pharmacy_sale_items table
|
|
||||||
CREATE TABLE IF NOT EXISTS pharmacy_sale_items (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
sale_id INT NOT NULL,
|
|
||||||
drug_id INT NOT NULL,
|
|
||||||
batch_id INT NULL, -- Can be null if we track sales without specific batch selection (though we should enforce it for stock deduction)
|
|
||||||
quantity INT NOT NULL DEFAULT 1,
|
|
||||||
unit_price DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
|
||||||
total_price DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (sale_id) REFERENCES pharmacy_sales(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (drug_id) REFERENCES drugs(id),
|
|
||||||
FOREIGN KEY (batch_id) REFERENCES pharmacy_batches(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Add stock management columns to drugs table
|
|
||||||
ALTER TABLE drugs ADD COLUMN IF NOT EXISTS min_stock_level INT DEFAULT 10;
|
|
||||||
ALTER TABLE drugs ADD COLUMN IF NOT EXISTS reorder_level INT DEFAULT 20;
|
|
||||||
ALTER TABLE drugs ADD COLUMN IF NOT EXISTS unit VARCHAR(50) DEFAULT 'pack';
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
-- Add batch and expiry to LPO items for receiving stock
|
|
||||||
ALTER TABLE pharmacy_lpo_items ADD COLUMN IF NOT EXISTS batch_number VARCHAR(50) NULL;
|
|
||||||
ALTER TABLE pharmacy_lpo_items ADD COLUMN IF NOT EXISTS expiry_date DATE NULL;
|
|
||||||
|
|
||||||
-- Create Purchase Returns table
|
|
||||||
CREATE TABLE IF NOT EXISTS pharmacy_purchase_returns (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
supplier_id INT NOT NULL,
|
|
||||||
return_date DATE NOT NULL,
|
|
||||||
total_amount DECIMAL(10, 2) DEFAULT 0.00,
|
|
||||||
reason TEXT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (supplier_id) REFERENCES suppliers(id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
-- Create Purchase Return Items table
|
|
||||||
CREATE TABLE IF NOT EXISTS pharmacy_purchase_return_items (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
return_id INT NOT NULL,
|
|
||||||
drug_id INT NOT NULL,
|
|
||||||
batch_id INT NULL, -- Which batch we are returning from
|
|
||||||
quantity INT NOT NULL,
|
|
||||||
unit_price DECIMAL(10, 2) NOT NULL, -- Refund price
|
|
||||||
total_price DECIMAL(10, 2) NOT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (return_id) REFERENCES pharmacy_purchase_returns(id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (drug_id) REFERENCES drugs(id),
|
|
||||||
FOREIGN KEY (batch_id) REFERENCES pharmacy_batches(id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE inventory_transactions ADD COLUMN IF NOT EXISTS department_id INT NULL;
|
|
||||||
ALTER TABLE inventory_transactions ADD CONSTRAINT fk_inventory_dept FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE SET NULL;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE insurance_payments ADD COLUMN payment_method VARCHAR(50) DEFAULT 'Check' AFTER reference_number;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE users ADD COLUMN avatar VARCHAR(255) DEFAULT NULL;
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE nurses ADD COLUMN IF NOT EXISTS employee_id INT;
|
|
||||||
ALTER TABLE nurses ADD CONSTRAINT fk_nurse_employee FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE SET NULL;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE drugs ADD COLUMN image VARCHAR(255) NULL;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE visits ADD COLUMN IF NOT EXISTS nursing_notes TEXT AFTER temperature;
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
-- Add user_id to employees to link with login
|
|
||||||
ALTER TABLE employees ADD COLUMN IF NOT EXISTS user_id INT NULL;
|
|
||||||
ALTER TABLE employees ADD COLUMN IF NOT EXISTS join_date DATE NULL;
|
|
||||||
|
|
||||||
-- Attendance
|
|
||||||
CREATE TABLE IF NOT EXISTS attendance_logs (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
employee_id INT NOT NULL,
|
|
||||||
date DATE NOT NULL,
|
|
||||||
check_in DATETIME NULL,
|
|
||||||
check_out DATETIME NULL,
|
|
||||||
status ENUM('Present', 'Late', 'Absent', 'On Leave') DEFAULT 'Present',
|
|
||||||
source VARCHAR(50) DEFAULT 'Web',
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Leaves
|
|
||||||
CREATE TABLE IF NOT EXISTS leave_requests (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
employee_id INT NOT NULL,
|
|
||||||
leave_type VARCHAR(50) NOT NULL,
|
|
||||||
start_date DATE NOT NULL,
|
|
||||||
end_date DATE NOT NULL,
|
|
||||||
days INT NOT NULL,
|
|
||||||
reason TEXT,
|
|
||||||
status ENUM('Pending', 'Approved', 'Rejected') DEFAULT 'Pending',
|
|
||||||
approved_by INT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Salaries / Payroll Info
|
|
||||||
CREATE TABLE IF NOT EXISTS employee_salaries (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
employee_id INT NOT NULL,
|
|
||||||
basic_salary DECIMAL(10, 2) DEFAULT 0.00,
|
|
||||||
housing_allowance DECIMAL(10, 2) DEFAULT 0.00,
|
|
||||||
transport_allowance DECIMAL(10, 2) DEFAULT 0.00,
|
|
||||||
other_allowance DECIMAL(10, 2) DEFAULT 0.00,
|
|
||||||
currency VARCHAR(10) DEFAULT 'USD',
|
|
||||||
effective_date DATE NOT NULL,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Biometric Devices (for API auth)
|
|
||||||
CREATE TABLE IF NOT EXISTS biometric_devices (
|
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
device_name VARCHAR(100) NOT NULL,
|
|
||||||
ip_address VARCHAR(50),
|
|
||||||
api_key VARCHAR(255) NOT NULL,
|
|
||||||
status TINYINT(1) DEFAULT 1,
|
|
||||||
last_seen DATETIME NULL
|
|
||||||
);
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
-- Fix appointments table schema: Add start_time/end_time if missing
|
|
||||||
-- This handles the case where init_db.php created the table with appointment_date instead of start_time
|
|
||||||
|
|
||||||
-- Add start_time if it doesn't exist
|
|
||||||
-- Note: MySQL/MariaDB < 10.2 don't support ADD COLUMN IF NOT EXISTS easily, so we use a stored procedure or just suppress errors in apply_migrations.php
|
|
||||||
-- Assuming MariaDB 10.2+ or suppression in apply_migrations.php for "Duplicate column"
|
|
||||||
|
|
||||||
ALTER TABLE appointments ADD COLUMN start_time DATETIME NULL;
|
|
||||||
ALTER TABLE appointments ADD COLUMN end_time DATETIME NULL;
|
|
||||||
|
|
||||||
-- Migrate data if appointment_date exists
|
|
||||||
-- Check if appointment_date exists first? SQL doesn't have conditional logic outside procedures easily.
|
|
||||||
-- We'll try to update, if column doesn't exist it will fail but that's fine if start_time is already populated.
|
|
||||||
-- Wait, if appointment_date doesn't exist, this query will fail and might stop migration script if not handled.
|
|
||||||
-- But apply_migrations.php continues on error? No, it catches exceptions per statement.
|
|
||||||
|
|
||||||
-- We can wrap in a procedure to be safe, but apply_migrations.php splits by ';'.
|
|
||||||
-- So let's just try the update. If appointment_date doesn't exist, it fails harmlessly.
|
|
||||||
|
|
||||||
UPDATE appointments SET start_time = appointment_date WHERE start_time IS NULL;
|
|
||||||
UPDATE appointments SET end_time = DATE_ADD(start_time, INTERVAL 30 MINUTE) WHERE end_time IS NULL AND start_time IS NOT NULL;
|
|
||||||
|
|
||||||
-- Make start_time NOT NULL after populating
|
|
||||||
ALTER TABLE appointments MODIFY COLUMN start_time DATETIME NOT NULL;
|
|
||||||
ALTER TABLE appointments MODIFY COLUMN end_time DATETIME NOT NULL;
|
|
||||||
|
|
||||||
-- Drop appointment_date if it exists (optional cleanup)
|
|
||||||
-- ALTER TABLE appointments DROP COLUMN appointment_date;
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
-- Migration to merge Doctors and Nurses into HR (Employees)
|
|
||||||
-- Step 1: Add new columns to hold Employee IDs
|
|
||||||
ALTER TABLE visits ADD COLUMN IF NOT EXISTS doctor_employee_id INT NULL;
|
|
||||||
ALTER TABLE appointments ADD COLUMN IF NOT EXISTS doctor_employee_id INT NULL;
|
|
||||||
ALTER TABLE appointments ADD COLUMN IF NOT EXISTS nurse_employee_id INT NULL;
|
|
||||||
|
|
||||||
-- Step 2: Migrate data (if doctors/nurses have employee_id set)
|
|
||||||
-- Update Visits
|
|
||||||
UPDATE visits v
|
|
||||||
JOIN doctors d ON v.doctor_id = d.id
|
|
||||||
SET v.doctor_employee_id = d.employee_id
|
|
||||||
WHERE d.employee_id IS NOT NULL;
|
|
||||||
|
|
||||||
-- Update Appointments (Doctor)
|
|
||||||
UPDATE appointments a
|
|
||||||
JOIN doctors d ON a.doctor_id = d.id
|
|
||||||
SET a.doctor_employee_id = d.employee_id
|
|
||||||
WHERE d.employee_id IS NOT NULL;
|
|
||||||
|
|
||||||
-- Update Appointments (Nurse)
|
|
||||||
UPDATE appointments a
|
|
||||||
JOIN nurses n ON a.nurse_id = n.id
|
|
||||||
SET a.nurse_employee_id = n.employee_id
|
|
||||||
WHERE n.employee_id IS NOT NULL;
|
|
||||||
|
|
||||||
-- Step 3: Drop old Foreign Keys (Constraint names might vary, so we try standard names or rely on DROP COLUMN to drop FKs in some DBs, but explicitly dropping FK is safer)
|
|
||||||
-- Finding constraint names is hard in SQL script without dynamic SQL.
|
|
||||||
-- However, in MariaDB/MySQL, dropping the column usually drops the FK.
|
|
||||||
-- But to be safe, we will try to drop the standard named constraints if known, or just proceed with DROP COLUMN which should work if no other constraints block it.
|
|
||||||
|
|
||||||
ALTER TABLE visits DROP FOREIGN KEY IF EXISTS visits_ibfk_2; -- doctor_id
|
|
||||||
ALTER TABLE visits DROP FOREIGN KEY IF EXISTS fk_visit_nurse;
|
|
||||||
ALTER TABLE visits DROP FOREIGN KEY IF EXISTS fk_visit_doctor_employee;
|
|
||||||
ALTER TABLE appointments DROP FOREIGN KEY IF EXISTS appointments_ibfk_2; -- doctor_id
|
|
||||||
ALTER TABLE appointments DROP FOREIGN KEY IF EXISTS appointments_ibfk_3; -- nurse_id
|
|
||||||
ALTER TABLE appointments DROP FOREIGN KEY IF EXISTS fk_appointment_nurse;
|
|
||||||
ALTER TABLE appointments DROP FOREIGN KEY IF EXISTS fk_appt_doctor_employee;
|
|
||||||
ALTER TABLE appointments DROP FOREIGN KEY IF EXISTS fk_appt_nurse_employee;
|
|
||||||
|
|
||||||
-- Also drop keys/indexes if they exist separate from FK
|
|
||||||
ALTER TABLE visits DROP KEY IF EXISTS doctor_id;
|
|
||||||
ALTER TABLE appointments DROP KEY IF EXISTS doctor_id;
|
|
||||||
ALTER TABLE appointments DROP KEY IF EXISTS nurse_id;
|
|
||||||
|
|
||||||
-- Step 4: Drop old columns
|
|
||||||
ALTER TABLE visits DROP COLUMN doctor_id;
|
|
||||||
ALTER TABLE appointments DROP COLUMN doctor_id;
|
|
||||||
ALTER TABLE appointments DROP COLUMN nurse_id;
|
|
||||||
|
|
||||||
-- Step 5: Rename new columns to match standard naming (or keep them and add FK)
|
|
||||||
-- Let's rename them back to doctor_id and nurse_id but now they point to employees
|
|
||||||
ALTER TABLE visits CHANGE COLUMN doctor_employee_id doctor_id INT NULL;
|
|
||||||
ALTER TABLE appointments CHANGE COLUMN doctor_employee_id doctor_id INT NULL;
|
|
||||||
ALTER TABLE appointments CHANGE COLUMN nurse_employee_id nurse_id INT NULL;
|
|
||||||
|
|
||||||
-- Step 6: Add new Foreign Keys to employees
|
|
||||||
ALTER TABLE visits ADD CONSTRAINT fk_visit_doctor_employee FOREIGN KEY (doctor_id) REFERENCES employees(id) ON DELETE SET NULL;
|
|
||||||
ALTER TABLE appointments ADD CONSTRAINT fk_appt_doctor_employee FOREIGN KEY (doctor_id) REFERENCES employees(id) ON DELETE SET NULL;
|
|
||||||
ALTER TABLE appointments ADD CONSTRAINT fk_appt_nurse_employee FOREIGN KEY (nurse_id) REFERENCES employees(id) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
-- Step 7: Drop obsolete tables
|
|
||||||
DROP TABLE IF EXISTS doctor_holidays; -- If exists
|
|
||||||
DROP TABLE IF EXISTS doctors;
|
|
||||||
DROP TABLE IF EXISTS nurses;
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
-- Final cleanup for Doctors/Nurses migration
|
|
||||||
|
|
||||||
-- Fix Patient Queue Doctor ID (References doctors)
|
|
||||||
ALTER TABLE patient_queue ADD COLUMN IF NOT EXISTS doctor_employee_id INT NULL;
|
|
||||||
|
|
||||||
-- Migrate data
|
|
||||||
UPDATE patient_queue q
|
|
||||||
JOIN doctors d ON q.doctor_id = d.id
|
|
||||||
SET q.doctor_employee_id = d.employee_id
|
|
||||||
WHERE d.employee_id IS NOT NULL;
|
|
||||||
|
|
||||||
-- Drop old FK
|
|
||||||
ALTER TABLE patient_queue DROP FOREIGN KEY IF EXISTS patient_queue_ibfk_3;
|
|
||||||
|
|
||||||
-- Drop old column
|
|
||||||
ALTER TABLE patient_queue DROP COLUMN doctor_id;
|
|
||||||
|
|
||||||
-- Rename new column
|
|
||||||
ALTER TABLE patient_queue CHANGE COLUMN doctor_employee_id doctor_id INT NULL;
|
|
||||||
|
|
||||||
-- Add new FK to employees
|
|
||||||
ALTER TABLE patient_queue ADD CONSTRAINT fk_queue_doctor_employee FOREIGN KEY (doctor_id) REFERENCES employees(id) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
-- Now drop doctors table
|
|
||||||
DROP TABLE IF EXISTS doctors;
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
-- Fix Migration: Merge Doctors and Nurses into HR (Employees) - Cleanup
|
|
||||||
|
|
||||||
-- 1. Drop dependent tables that reference doctors
|
|
||||||
DROP TABLE IF EXISTS doctor_schedules;
|
|
||||||
|
|
||||||
-- 2. Fix Visits Nurse ID
|
|
||||||
-- Add temp column if it doesn't exist (it wasn't added in previous migration)
|
|
||||||
ALTER TABLE visits ADD COLUMN IF NOT EXISTS nurse_employee_id INT NULL;
|
|
||||||
|
|
||||||
-- Migrate data from nurses table to visits.nurse_employee_id
|
|
||||||
UPDATE visits v
|
|
||||||
JOIN nurses n ON v.nurse_id = n.id
|
|
||||||
SET v.nurse_employee_id = n.employee_id
|
|
||||||
WHERE n.employee_id IS NOT NULL;
|
|
||||||
|
|
||||||
-- Drop old FK on visits.nurse_id
|
|
||||||
ALTER TABLE visits DROP FOREIGN KEY IF EXISTS fk_visit_nurse;
|
|
||||||
|
|
||||||
-- Drop old column visits.nurse_id
|
|
||||||
-- We use a check to avoid error if it was already dropped (though unlikely)
|
|
||||||
ALTER TABLE visits DROP COLUMN nurse_id;
|
|
||||||
|
|
||||||
-- Rename new column to nurse_id
|
|
||||||
ALTER TABLE visits CHANGE COLUMN nurse_employee_id nurse_id INT NULL;
|
|
||||||
|
|
||||||
-- Add new FK to employees
|
|
||||||
ALTER TABLE visits DROP FOREIGN KEY IF EXISTS fk_visit_nurse_employee;
|
|
||||||
ALTER TABLE visits ADD CONSTRAINT fk_visit_nurse_employee FOREIGN KEY (nurse_id) REFERENCES employees(id) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
|
|
||||||
-- 3. Fix Appointments Nurse ID
|
|
||||||
-- Ensure nurse_employee_id exists (might have been created in previous migration)
|
|
||||||
ALTER TABLE appointments ADD COLUMN IF NOT EXISTS nurse_employee_id INT NULL;
|
|
||||||
|
|
||||||
-- Migrate data again just in case
|
|
||||||
UPDATE appointments a
|
|
||||||
JOIN nurses n ON a.nurse_id = n.id
|
|
||||||
SET a.nurse_employee_id = n.employee_id
|
|
||||||
WHERE n.employee_id IS NOT NULL;
|
|
||||||
|
|
||||||
-- Drop old FK on appointments.nurse_id
|
|
||||||
ALTER TABLE appointments DROP FOREIGN KEY IF EXISTS fk_appointment_nurse;
|
|
||||||
|
|
||||||
-- Drop old column appointments.nurse_id
|
|
||||||
ALTER TABLE appointments DROP COLUMN nurse_id;
|
|
||||||
|
|
||||||
-- Rename new column to nurse_id
|
|
||||||
ALTER TABLE appointments CHANGE COLUMN nurse_employee_id nurse_id INT NULL;
|
|
||||||
|
|
||||||
-- Add new FK to employees
|
|
||||||
ALTER TABLE appointments DROP FOREIGN KEY IF EXISTS fk_appt_nurse_employee;
|
|
||||||
ALTER TABLE appointments ADD CONSTRAINT fk_appt_nurse_employee FOREIGN KEY (nurse_id) REFERENCES employees(id) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
|
|
||||||
-- 4. Drop obsolete tables
|
|
||||||
-- Now that FKs are gone, this should succeed.
|
|
||||||
DROP TABLE IF EXISTS doctors;
|
|
||||||
DROP TABLE IF EXISTS nurses;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE departments ADD COLUMN active BOOLEAN DEFAULT 1;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
ALTER TABLE employees ADD COLUMN room_number VARCHAR(50) NULL AFTER email;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE departments ADD COLUMN requires_vitals TINYINT(1) DEFAULT 0;
|
|
||||||
ALTER TABLE departments ADD COLUMN is_vitals_room TINYINT(1) DEFAULT 0;
|
|
||||||
ALTER TABLE patient_queue ADD COLUMN target_department_id INT(11) DEFAULT NULL;
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
-- Seed positions and employees if none exist (useful for fresh installs)
|
|
||||||
INSERT IGNORE INTO positions (id, name_en, name_ar, description_en, description_ar) VALUES
|
|
||||||
(1, 'Doctor', 'طبيب', 'Medical Doctor', 'طبيب بشري'),
|
|
||||||
(2, 'Nurse', 'ممرض', 'Registered Nurse', 'ممرض مسجل'),
|
|
||||||
(3, 'Receptionist', 'موظف استقبال', 'Front Desk', 'الاستقبال');
|
|
||||||
|
|
||||||
INSERT IGNORE INTO employees (id, name_en, name_ar, email, mobile, department_id, position_id, room_number) VALUES
|
|
||||||
(1, 'Dr. Ahmed Ali', 'د. أحمد علي', 'ahmed@hospital.com', '0501234567', 1, 1, '101'),
|
|
||||||
(2, 'Dr. Sarah Smith', 'د. سارة سميث', 'sarah@hospital.com', '0501234568', 2, 1, '102'),
|
|
||||||
(3, 'Dr. John Doe', 'د. جون دو', 'john@hospital.com', '0501234569', 4, 1, '103'),
|
|
||||||
(4, 'Nurse Fatima', 'الممرضة فاطمة', 'fatima@hospital.com', '0501234570', 3, 2, 'ER-1'),
|
|
||||||
(5, 'Nurse Mary', 'الممرضة ماري', 'mary@hospital.com', '0501234571', 1, 2, 'OPD-1');
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'db/config.php';
|
|
||||||
$pdo = db();
|
|
||||||
|
|
||||||
echo "<h2>Patient Queue (Latest 5)</h2>";
|
|
||||||
$stmt = $pdo->query("SELECT * FROM patient_queue ORDER BY id DESC LIMIT 5");
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
if ($rows) {
|
|
||||||
echo "<table border='1'><tr>";
|
|
||||||
foreach (array_keys($rows[0]) as $k) echo "<th>$k</th>";
|
|
||||||
echo "</tr>";
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
echo "<tr>";
|
|
||||||
foreach ($row as $v) echo "<td>$v</td>";
|
|
||||||
echo "</tr>";
|
|
||||||
}
|
|
||||||
echo "</table>";
|
|
||||||
} else {
|
|
||||||
echo "No records in patient_queue.<br>";
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "<h2>Visits (Latest 5)</h2>";
|
|
||||||
$stmt = $pdo->query("SELECT id, patient_id, doctor_id, created_at FROM visits ORDER BY id DESC LIMIT 5");
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
if ($rows) {
|
|
||||||
echo "<table border='1'><tr>";
|
|
||||||
foreach (array_keys($rows[0]) as $k) echo "<th>$k</th>";
|
|
||||||
echo "</tr>";
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
echo "<tr>";
|
|
||||||
foreach ($row as $v) echo "<td>$v</td>";
|
|
||||||
echo "</tr>";
|
|
||||||
}
|
|
||||||
echo "</table>";
|
|
||||||
} else {
|
|
||||||
echo "No records in visits.<br>";
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
$db = db();
|
|
||||||
// Delete the test appointment created by test_create_appointment_curl.php (ID 14)
|
|
||||||
// Use a safe check to only delete if it looks like a test
|
|
||||||
$id = 14;
|
|
||||||
$stmt = $db->prepare("DELETE FROM appointments WHERE id = ? AND reason = 'Test API'");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
echo "Deleted test appointment $id (if it matched).\n";
|
|
||||||
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
|
|
||||||
$db = db();
|
|
||||||
$id = 15;
|
|
||||||
|
|
||||||
$stmt = $db->prepare("DELETE FROM appointments WHERE id = ?");
|
|
||||||
$stmt->execute([$id]);
|
|
||||||
|
|
||||||
echo "Deleted appointment $id\n";
|
|
||||||
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
$section = 'departments';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
|
|
||||||
$db = db();
|
|
||||||
$lang = $_SESSION['lang'];
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
require_once __DIR__ . '/includes/pages/departments.php';
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
22
drugs.php
22
drugs.php
@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
$section = 'drugs';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
$db = db();
|
|
||||||
$lang = $_SESSION['lang'] ?? 'en';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
|
|
||||||
if (!isset($_GET['ajax_search'])) {
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/pages/drugs.php';
|
|
||||||
|
|
||||||
if (!isset($_GET['ajax_search'])) {
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
$section = 'drugs_groups';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
$db = db();
|
|
||||||
$lang = $_SESSION['lang'] ?? 'en';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
|
|
||||||
$is_ajax = (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
|
|
||||||
|
|
||||||
if (!$is_ajax) {
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/pages/drugs_groups.php';
|
|
||||||
|
|
||||||
if (!$is_ajax) {
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
session_start();
|
|
||||||
if (!isset($_SESSION['lang'])) {
|
|
||||||
$_SESSION['lang'] = 'en';
|
|
||||||
}
|
|
||||||
if (isset($_GET['lang'])) {
|
|
||||||
$_SESSION['lang'] = $_GET['lang'] === 'ar' ? 'ar' : 'en';
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once 'db/config.php';
|
|
||||||
require_once 'lang.php';
|
|
||||||
require_once 'helpers.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
|
|
||||||
$db = db();
|
|
||||||
$lang = $_SESSION['lang'];
|
|
||||||
$section = 'employees';
|
|
||||||
|
|
||||||
require_once 'includes/actions.php';
|
|
||||||
require_once 'includes/common_data.php';
|
|
||||||
|
|
||||||
include 'includes/layout/header.php';
|
|
||||||
include 'includes/pages/employees.php';
|
|
||||||
include 'includes/layout/footer.php';
|
|
||||||
132
helpers.php
132
helpers.php
@ -1,132 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
$SYSTEM_SETTINGS = null;
|
|
||||||
|
|
||||||
function get_system_settings() {
|
|
||||||
global $db, $SYSTEM_SETTINGS;
|
|
||||||
|
|
||||||
if ($SYSTEM_SETTINGS !== null) {
|
|
||||||
return $SYSTEM_SETTINGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($db)) {
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
try {
|
|
||||||
$local_db = db();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// If DB connection fails, return empty settings instead of crashing
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$local_db = $db;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmt = $local_db->query('SELECT setting_key, setting_value FROM settings');
|
|
||||||
$settings = [];
|
|
||||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
|
||||||
$settings[$row['setting_key']] = $row['setting_value'];
|
|
||||||
}
|
|
||||||
$SYSTEM_SETTINGS = $settings;
|
|
||||||
return $settings;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function apply_timezone() {
|
|
||||||
try {
|
|
||||||
$s = get_system_settings();
|
|
||||||
if (!empty($s['timezone'])) {
|
|
||||||
date_default_timezone_set($s['timezone']);
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// Ignore timezone errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
apply_timezone();
|
|
||||||
|
|
||||||
function format_currency($amount) {
|
|
||||||
$settings = get_system_settings();
|
|
||||||
$currency_symbol = $settings['currency_symbol'] ?? '$';
|
|
||||||
$decimal_digits = isset($settings['decimal_digits']) ? (int)$settings['decimal_digits'] : 2;
|
|
||||||
|
|
||||||
return $currency_symbol . ' ' . number_format((float)$amount, $decimal_digits);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only start session if not already started
|
|
||||||
if (session_status() === PHP_SESSION_NONE) {
|
|
||||||
session_start();
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/lang.php';
|
|
||||||
|
|
||||||
if (!isset($_SESSION['lang'])) {
|
|
||||||
$_SESSION['lang'] = 'en';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($_GET['lang'])) {
|
|
||||||
if ($_GET['lang'] === 'ar' || $_GET['lang'] === 'en') {
|
|
||||||
$_SESSION['lang'] = $_GET['lang'];
|
|
||||||
// Redirect to remove lang param
|
|
||||||
if (!headers_sent()) {
|
|
||||||
header("Location: " . strtok($_SERVER["REQUEST_URI"], '?'));
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function __($key) {
|
|
||||||
global $translations;
|
|
||||||
$lang = $_SESSION['lang'] ?? 'en'; // Fallback if session is empty
|
|
||||||
return $translations[$lang][$key] ?? $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
function is_rtl() {
|
|
||||||
return ($_SESSION['lang'] ?? 'en') === 'ar';
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_dir() {
|
|
||||||
return is_rtl() ? 'rtl' : 'ltr';
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_lang_name() {
|
|
||||||
return ($_SESSION['lang'] ?? 'en') === 'ar' ? 'English' : 'العربية';
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_lang_code() {
|
|
||||||
return $_SESSION['lang'] ?? 'en';
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculate_age($dob) {
|
|
||||||
if (empty($dob)) return '-';
|
|
||||||
try {
|
|
||||||
$birthDate = new DateTime($dob);
|
|
||||||
$today = new DateTime('today');
|
|
||||||
return $birthDate->diff($today)->y;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!function_exists('mb_strimwidth')) {
|
|
||||||
function mb_strimwidth($string, $start, $width, $trimmarker = '...', $encoding = null) {
|
|
||||||
// Simple polyfill using substr
|
|
||||||
// 1. Handle start offset
|
|
||||||
$string = (string)$string;
|
|
||||||
if ($start > 0) {
|
|
||||||
$string = substr($string, $start);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Check length
|
|
||||||
if (strlen($string) <= $width) {
|
|
||||||
return $string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Truncate
|
|
||||||
$targetLen = $width - strlen($trimmarker);
|
|
||||||
if ($targetLen < 0) $targetLen = 0;
|
|
||||||
|
|
||||||
return substr($string, 0, $targetLen) . $trimmarker;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
|
|
||||||
// Check for user role if needed (e.g. only doctors/admins/nurses)
|
|
||||||
// For now, accessible to all logged in users
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/pages/home_visits.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
?>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
|
|
||||||
$db = db();
|
|
||||||
|
|
||||||
// Handle Form Submissions
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
if (isset($_POST['action'])) {
|
|
||||||
try {
|
|
||||||
if ($_POST['action'] === 'add_service') {
|
|
||||||
$stmt = $db->prepare("INSERT INTO services (name_en, name_ar, department_id, price, is_active) VALUES (?, ?, ?, ?, ?)");
|
|
||||||
$stmt->execute([
|
|
||||||
$_POST['name_en'],
|
|
||||||
$_POST['name_ar'],
|
|
||||||
$_POST['department_id'],
|
|
||||||
$_POST['price'],
|
|
||||||
isset($_POST['is_active']) ? 1 : 0
|
|
||||||
]);
|
|
||||||
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('service_added_successfully') . '</div>';
|
|
||||||
} elseif ($_POST['action'] === 'edit_service') {
|
|
||||||
$stmt = $db->prepare("UPDATE services SET name_en = ?, name_ar = ?, department_id = ?, price = ?, is_active = ? WHERE id = ?");
|
|
||||||
$stmt->execute([
|
|
||||||
$_POST['name_en'],
|
|
||||||
$_POST['name_ar'],
|
|
||||||
$_POST['department_id'],
|
|
||||||
$_POST['price'],
|
|
||||||
isset($_POST['is_active']) ? 1 : 0,
|
|
||||||
$_POST['id']
|
|
||||||
]);
|
|
||||||
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('service_updated_successfully') . '</div>';
|
|
||||||
} elseif ($_POST['action'] === 'delete_service') {
|
|
||||||
$stmt = $db->prepare("DELETE FROM services WHERE id = ?");
|
|
||||||
$stmt->execute([$_POST['id']]);
|
|
||||||
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('service_deleted_successfully') . '</div>';
|
|
||||||
}
|
|
||||||
// Redirect after successful operation
|
|
||||||
header("Location: hospital_services.php");
|
|
||||||
exit;
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('error') . ': ' . $e->getMessage() . '</div>';
|
|
||||||
// Redirect even on error, so the user sees the message
|
|
||||||
header("Location: hospital_services.php");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session check logic (if needed in future)
|
|
||||||
// if (!isset($_SESSION['user_id'])) { ... }
|
|
||||||
|
|
||||||
$section = 'services';
|
|
||||||
$title = __('services');
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
require_once __DIR__ . '/includes/pages/services.php';
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Enable detailed error reporting for debugging
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
ini_set('display_startup_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
$section = 'hr_attendance';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
|
|
||||||
// Try to connect to DB first
|
|
||||||
try {
|
|
||||||
$db = db();
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
die("Database Connection Error: " . $e->getMessage());
|
|
||||||
} catch (Exception $e) {
|
|
||||||
die("General Error: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
|
|
||||||
$lang = $_SESSION['lang'];
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/pages/hr_attendance.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Enable detailed error reporting for debugging
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
ini_set('display_startup_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
$section = 'hr_dashboard';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
|
|
||||||
// Try to connect to DB first
|
|
||||||
try {
|
|
||||||
$db = db();
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
die("Database Connection Error: " . $e->getMessage());
|
|
||||||
} catch (Exception $e) {
|
|
||||||
die("General Error: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
|
|
||||||
$lang = $_SESSION['lang'];
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/pages/hr_dashboard.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Enable detailed error reporting for debugging
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
ini_set('display_startup_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
$section = 'hr_leaves';
|
|
||||||
require_once __DIR__ . '/db/config.php';
|
|
||||||
|
|
||||||
// Try to connect to DB first
|
|
||||||
try {
|
|
||||||
$db = db();
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
die("Database Connection Error: " . $e->getMessage());
|
|
||||||
} catch (Exception $e) {
|
|
||||||
die("General Error: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/helpers.php';
|
|
||||||
require_once __DIR__ . '/includes/auth.php';
|
|
||||||
check_auth();
|
|
||||||
|
|
||||||
$lang = $_SESSION['lang'];
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/actions.php';
|
|
||||||
require_once __DIR__ . '/includes/common_data.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/layout/header.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/pages/hr_leaves.php';
|
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/layout/footer.php';
|
|
||||||
File diff suppressed because it is too large
Load Diff
1245
includes/actions.php
1245
includes/actions.php
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user