diff --git a/api/billing.php b/api/billing.php new file mode 100644 index 0000000..a11f2ab --- /dev/null +++ b/api/billing.php @@ -0,0 +1,274 @@ + 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]); +} diff --git a/check_details_schema.php b/check_details_schema.php new file mode 100644 index 0000000..b24167e --- /dev/null +++ b/check_details_schema.php @@ -0,0 +1,16 @@ +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"; + } +} + diff --git a/check_prices_schema.php b/check_prices_schema.php new file mode 100644 index 0000000..4a4c7d9 --- /dev/null +++ b/check_prices_schema.php @@ -0,0 +1,16 @@ +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"; + } +} + diff --git a/check_schema_billing.php b/check_schema_billing.php new file mode 100644 index 0000000..9494a4c --- /dev/null +++ b/check_schema_billing.php @@ -0,0 +1,16 @@ +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"; + } +} + diff --git a/dashboard.php b/dashboard.php index b1df3bd..e9aa3e8 100644 --- a/dashboard.php +++ b/dashboard.php @@ -8,6 +8,13 @@ $lang = $_SESSION['lang']; require_once __DIR__ . '/includes/actions.php'; require_once __DIR__ . '/includes/common_data.php'; -require_once __DIR__ . '/includes/layout/header.php'; + +if (!isset($_GET['ajax_search'])) { + require_once __DIR__ . '/includes/layout/header.php'; +} + require_once __DIR__ . '/includes/pages/dashboard.php'; -require_once __DIR__ . '/includes/layout/footer.php'; + +if (!isset($_GET['ajax_search'])) { + require_once __DIR__ . '/includes/layout/footer.php'; +} \ No newline at end of file diff --git a/db/migrations/20260321_add_billing_columns.sql b/db/migrations/20260321_add_billing_columns.sql new file mode 100644 index 0000000..97da454 --- /dev/null +++ b/db/migrations/20260321_add_billing_columns.sql @@ -0,0 +1,3 @@ +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; diff --git a/db/migrations/20260321_add_payment_method_to_bills.sql b/db/migrations/20260321_add_payment_method_to_bills.sql new file mode 100644 index 0000000..3938a56 --- /dev/null +++ b/db/migrations/20260321_add_payment_method_to_bills.sql @@ -0,0 +1,2 @@ +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; diff --git a/db/migrations/20260321_add_status_to_visits.sql b/db/migrations/20260321_add_status_to_visits.sql new file mode 100644 index 0000000..1d19130 --- /dev/null +++ b/db/migrations/20260321_add_status_to_visits.sql @@ -0,0 +1,2 @@ +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; diff --git a/includes/common_data.php b/includes/common_data.php index 5c21dcf..6601b96 100644 --- a/includes/common_data.php +++ b/includes/common_data.php @@ -1,7 +1,7 @@ query("SELECT id, name_$lang as name FROM doctors")->fetchAll(); -$all_patients = $db->query("SELECT id, name, dob, gender FROM patients")->fetchAll(); +$all_patients = $db->query("SELECT id, name, phone, civil_id, dob, gender FROM patients")->fetchAll(); $all_nurses = $db->query("SELECT id, name_$lang as name FROM nurses")->fetchAll(); $all_departments = $db->query("SELECT id, name_$lang as name FROM departments")->fetchAll(); $all_employees = $db->query("SELECT id, name_$lang as name FROM employees")->fetchAll(); @@ -9,13 +9,14 @@ $all_positions = $db->query("SELECT id, name_$lang as name FROM positions")->fet $all_insurance = $db->query("SELECT id, name_$lang as name FROM insurance_companies")->fetchAll(); $all_test_groups = $db->query("SELECT id, name_$lang as name FROM test_groups")->fetchAll(); $all_tests = $db->query("SELECT id, name_$lang as name, price, normal_range FROM laboratory_tests")->fetchAll(); +$all_services = $db->query("SELECT id, name_$lang as name, price FROM services WHERE is_active = 1")->fetchAll(); // X-Ray Data $all_xray_groups = $db->query("SELECT id, name_$lang as name FROM xray_groups")->fetchAll(); $all_xrays = $db->query("SELECT id, name_$lang as name, price FROM xray_tests")->fetchAll(); // Drugs Data -$all_drugs = $db->query("SELECT id, name_$lang as name, default_dosage, default_instructions FROM drugs")->fetchAll(); +$all_drugs = $db->query("SELECT id, name_$lang as name, default_dosage, default_instructions, price FROM drugs")->fetchAll(); $scheduled_appointments = $db->query(" SELECT a.id, p.name as patient_name, a.start_time, a.patient_id, a.doctor_id @@ -24,4 +25,4 @@ $scheduled_appointments = $db->query(" WHERE a.status = 'Scheduled' ORDER BY a.start_time ASC")->fetchAll();$all_countries = require __DIR__ . "/countries.php"; -$all_cities = $db->query("SELECT id, name_$lang as name FROM cities")->fetchAll(); \ No newline at end of file +$all_cities = $db->query("SELECT id, name_$lang as name FROM cities")->fetchAll(); diff --git a/includes/pages/dashboard.php b/includes/pages/dashboard.php index 09e182c..eeb0685 100644 --- a/includes/pages/dashboard.php +++ b/includes/pages/dashboard.php @@ -1,13 +1,85 @@ prepare($query); + $stmt->execute($params); + $patients = $stmt->fetchAll(); + + ob_start(); + if (empty($patients)): ?> + + + + +
+ () + + + + + + + + + + + + + $html]); + exit; +} + // Fetch Stats $total_patients = $db->query("SELECT COUNT(*) FROM patients")->fetchColumn(); -$today_appointments = $db->query("SELECT COUNT(*) FROM appointments WHERE DATE(start_time) = CURDATE()")->fetchColumn(); +$today_appointments_count = $db->query("SELECT COUNT(*) FROM appointments WHERE DATE(start_time) = CURDATE()")->fetchColumn(); $total_visits = $db->query("SELECT COUNT(*) FROM visits")->fetchColumn(); $total_revenue = $db->query("SELECT SUM(total_amount) FROM bills WHERE status = 'Paid'")->fetchColumn() ?: 0; $pending_revenue = $db->query("SELECT SUM(total_amount) FROM bills WHERE status = 'Pending'")->fetchColumn() ?: 0; $total_xrays = $db->query("SELECT COUNT(*) FROM xray_inquiries")->fetchColumn(); $total_labs = $db->query("SELECT COUNT(*) FROM laboratory_inquiries")->fetchColumn(); +// Fetch Running Visits (Not Completed/Cancelled) +$running_visits_sql = " + SELECT v.*, p.name as patient_name, d.name_$lang as doctor_name, n.name_$lang as nurse_name + FROM visits v + JOIN patients p ON v.patient_id = p.id + LEFT JOIN doctors d ON v.doctor_id = d.id + LEFT JOIN nurses n ON v.nurse_id = n.id + WHERE v.status IN ('CheckIn', 'In Progress') + ORDER BY v.visit_date ASC"; +$running_visits = $db->query($running_visits_sql)->fetchAll(); + +// Initial Patients Load $patients_sql = " SELECT p.*, ic.name_$lang as insurance_name FROM patients p @@ -15,13 +87,15 @@ $patients_sql = " ORDER BY p.id DESC LIMIT 5"; $patients = $db->query($patients_sql)->fetchAll(); +// Today's Appointments $appointments_sql = " - SELECT a.*, p.name as patient_name, d.name_$lang as doctor_name + SELECT a.*, p.name as patient_name, d.name_$lang as doctor_name, n.name_$lang as nurse_name FROM appointments a JOIN patients p ON a.patient_id = p.id - JOIN doctors d ON a.doctor_id = d.id - ORDER BY a.start_time DESC - LIMIT 5"; + LEFT JOIN doctors d ON a.doctor_id = d.id + LEFT JOIN nurses n ON a.nurse_id = n.id + WHERE DATE(a.start_time) = CURDATE() + ORDER BY a.start_time ASC"; $appointments = $db->query($appointments_sql)->fetchAll(); ?> @@ -37,7 +111,7 @@ $appointments = $db->query($appointments_sql)->fetchAll();
-

+

@@ -86,7 +160,7 @@ $appointments = $db->query($appointments_sql)->fetchAll(); - + + + +
+
+ + + + + + + + + + - - + + @@ -132,37 +328,572 @@ $appointments = $db->query($appointments_sql)->fetchAll(); -
-
-
-
+
+ + +
+
+ () +
+ + +
No patients found.
- - - - - - - - - - - - - - - - - - - - -
No appointments found.
+ +
- \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/lang.php b/lang.php index 88bc650..fe3c508 100644 --- a/lang.php +++ b/lang.php @@ -320,6 +320,10 @@ $translations = [ 'select_provider_type' => 'Select Provider Type', 'select_nurse' => 'Select Nurse', 'holiday' => 'Holiday', + 'checkout_payment' => 'Checkout Payment', + 'checkout' => 'Checkout', + 'print_bill' => 'Print Bill', + 'invoice' => 'Invoice', ], 'ar' => [ 'attachment' => 'المرفق', @@ -583,7 +587,7 @@ $translations = [ 'civil_id' => 'الرقم المدني', 'nationality' => 'الجنسية', 'city' => 'المدينة', - 'services' => 'الخدمات', + 'services' => 'Services', 'add_service' => 'إضافة خدمة', 'edit_service' => 'تعديل خدمة', 'delete_service' => 'حذف خدمة', @@ -643,5 +647,9 @@ $translations = [ 'select_provider_type' => 'اختر نوع مقدم الخدمة', 'select_nurse' => 'اختر الممرضة', 'holiday' => 'إجازة', + 'checkout_payment' => 'الدفع والمغادرة', + 'checkout' => 'الدفع', + 'print_bill' => 'طباعة الفاتورة', + 'invoice' => 'فاتورة', ] -]; +]; \ No newline at end of file diff --git a/list_tables.php b/list_tables.php new file mode 100644 index 0000000..d1a5a9b --- /dev/null +++ b/list_tables.php @@ -0,0 +1,7 @@ +query("SHOW TABLES"); +$tables = $stmt->fetchAll(PDO::FETCH_COLUMN); +echo implode("\n", $tables); + diff --git a/print_bill.php b/print_bill.php new file mode 100644 index 0000000..a2f8df3 --- /dev/null +++ b/print_bill.php @@ -0,0 +1,204 @@ +prepare("SELECT * FROM bills WHERE id = ?"); + $stmt->execute([$bill_id]); + $bill = $stmt->fetch(); + + if (!$bill) { + throw new Exception("Bill not found"); + } + + // Fetch Visit and Patient Details + $stmt = $db->prepare(" + SELECT + v.*, + p.name as patient_name, + p.phone as patient_phone, + p.civil_id, + d.name_en as doctor_name_en, + d.name_ar as doctor_name_ar + FROM visits v + JOIN patients p ON v.patient_id = p.id + LEFT JOIN doctors d ON v.doctor_id = d.id + WHERE v.id = ? + "); + $stmt->execute([$bill['visit_id']]); + $visit = $stmt->fetch(); + + // Fetch Bill Items + $stmt = $db->prepare("SELECT * FROM bill_items WHERE bill_id = ?"); + $stmt->execute([$bill_id]); + $items = $stmt->fetchAll(); + + // Fetch Company Settings (Logo, Address, etc.) + $stmt = $db->query("SELECT * FROM settings WHERE id = 1"); + $settings = $stmt->fetch(); + + $lang = $_SESSION['lang'] ?? 'en'; +} catch (Exception $e) { + die("Error: " . $e->getMessage()); +} +?> + + + + + + Invoice #<?php echo $bill_id; ?> + + + + + +
+
+ + +
+ +
+ +
+
+
+ + + +

+ +
+
+ Phone:
+ Email: +
+
+
+

+

#INV-

+

Date:

+
+
+
+ + +
+
+
Bill To:
+
+

+ '; ?> + +

+
+
+
Visit Details:
+

+ Doctor:
+ Visit ID: #
+ Status: +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + 0): ?> + + + + + + + + + + +
#
$
Total Amount$
Insurance Covered-$
Patient Due$
+ + +
+
+ +
+ PAID via +
+ +
+ PENDING +
+ + + +

Notes:

+ +
+
+ +

+

_________________________

+

Authorized Signature

+
+
+ + + +
+
+ + + \ No newline at end of file