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)): ?> +