updating billing

This commit is contained in:
Flatlogic Bot 2026-03-21 07:38:35 +00:00
parent 43f495d39b
commit 01f56287c6
13 changed files with 1344 additions and 57 deletions

274
api/billing.php Normal file
View File

@ -0,0 +1,274 @@
<?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]);
}

16
check_details_schema.php Normal file
View File

@ -0,0 +1,16 @@
<?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";
}
}

16
check_prices_schema.php Normal file
View File

@ -0,0 +1,16 @@
<?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";
}
}

16
check_schema_billing.php Normal file
View File

@ -0,0 +1,16 @@
<?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";
}
}

View File

@ -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';
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,7 +1,7 @@
<?php
// Common data for selects
$all_doctors = $db->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

View File

@ -1,13 +1,85 @@
<?php
// --- AJAX HANDLER FOR PATIENT SEARCH ---
if (isset($_GET['ajax_search'])) {
$search_query = $_GET['search'] ?? '';
$limit = 10;
$where = "WHERE 1=1";
$params = [];
if ($search_query) {
$where .= " AND (p.name LIKE ? OR p.phone LIKE ? OR p.civil_id LIKE ?)";
$params[] = "%$search_query%";
$params[] = "%$search_query%";
$params[] = "%$search_query%";
}
$query = "
SELECT p.*, ic.name_$lang as insurance_name
FROM patients p
LEFT JOIN insurance_companies ic ON p.insurance_company_id = ic.id
$where
ORDER BY p.id DESC
LIMIT $limit";
$stmt = $db->prepare($query);
$stmt->execute($params);
$patients = $stmt->fetchAll();
ob_start();
if (empty($patients)): ?>
<tr><td colspan="5" class="text-center py-3 text-muted"><?php echo __('no_patients_found'); ?></td></tr>
<?php else:
foreach ($patients as $p): ?>
<tr>
<td>
<div class="fw-bold"><?php echo htmlspecialchars($p['name']); ?></div>
<small class="text-muted"><?php echo $p['dob']; ?> (<?php echo calculate_age($p['dob']); ?>)</small>
</td>
<td><?php echo htmlspecialchars($p['phone']); ?></td>
<td>
<span class="badge <?php echo $p['insurance_name'] ? 'bg-primary' : 'bg-secondary'; ?>">
<?php echo $p['insurance_name'] ?: __('not_insured'); ?>
</span>
</td>
<td class="text-end">
<button class="btn btn-sm btn-outline-info" onclick="showReceptionistVisitModal(<?php echo $p['id']; ?>)" title="<?php echo __('add_visit'); ?>">
<i class="bi bi-clipboard-plus"></i>
</button>
<button class="btn btn-sm btn-outline-primary" onclick="showEditPatientModal(<?php echo htmlspecialchars(json_encode($p, JSON_UNESCAPED_UNICODE)); ?>)" title="<?php echo __('edit'); ?>">
<i class="bi bi-pencil-square"></i>
</button>
</td>
</tr>
<?php endforeach;
endif;
$html = ob_get_clean();
header('Content-Type: application/json');
echo json_encode(['html' => $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();
<div class="col-md-3 mb-3">
<div class="card stat-card h-100">
<i class="bi bi-calendar-check"></i>
<h3><?php echo $today_appointments; ?></h3>
<h3><?php echo $today_appointments_count; ?></h3>
<p class="text-muted mb-0"><?php echo __('today_appointments'); ?></p>
</div>
</div>
@ -86,7 +160,7 @@ $appointments = $db->query($appointments_sql)->fetchAll();
<button class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#bookAppointmentModal">
<i class="bi bi-calendar-plus"></i> <?php echo __('book_appointment'); ?>
</button>
<button class="btn btn-info btn-sm text-white" data-bs-toggle="modal" data-bs-target="#recordVisitModal">
<button class="btn btn-info btn-sm text-white" onclick="showReceptionistVisitModal()">
<i class="bi bi-clipboard-plus"></i> <?php echo __('add_visit'); ?>
</button>
<button class="btn btn-warning btn-sm text-white" data-bs-toggle="modal" data-bs-target="#addXrayInquiryModal">
@ -97,31 +171,153 @@ $appointments = $db->query($appointments_sql)->fetchAll();
</div>
</div>
<!-- Tables Section -->
<div class="row">
<div class="col-lg-6 mb-4">
<div class="card shadow-sm h-100">
<div class="card-header py-3">
<h6 class="mb-0 fw-bold text-white"><i class="bi bi-people-fill me-2"></i> <?php echo __('patients'); ?></h6>
<!-- Running Visits Table -->
<div class="row mb-4">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header py-3 d-flex justify-content-between align-items-center bg-primary text-white">
<h6 class="mb-0 fw-bold"><i class="bi bi-play-circle-fill me-2"></i> <?php echo __('running_visits'); ?></h6>
<span class="badge bg-white text-primary"><?php echo count($running_visits); ?></span>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th><?php echo __('name'); ?></th>
<th><?php echo __('age'); ?></th>
<th><?php echo __('phone'); ?></th>
<th><?php echo __('insurance'); ?></th>
<th><?php echo __('time'); ?></th>
<th><?php echo __('patient'); ?></th>
<th><?php echo __('doctor'); ?></th>
<th><?php echo __('status'); ?></th>
<th class="text-end"><?php echo __('actions'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($running_visits as $v): ?>
<tr>
<td><?php echo date('Y-m-d H:i', strtotime($v['visit_date'])); ?></td>
<td>
<div class="fw-bold"><?php echo htmlspecialchars($v['patient_name']); ?></div>
</td>
<td>
<?php
if ($v['doctor_name']) echo htmlspecialchars($v['doctor_name']);
elseif ($v['nurse_name']) echo htmlspecialchars($v['nurse_name']) . ' (' . __('nurse') . ')';
else echo '-';
?>
</td>
<td><span class="badge bg-warning text-dark"><?php echo __($v['status']); ?></span></td>
<td class="text-end">
<button class="btn btn-sm btn-outline-success" onclick="showCheckoutModal(<?php echo $v['id']; ?>)" title="<?php echo __('checkout_payment'); ?>">
<i class="bi bi-cash-stack"></i> <?php echo __('checkout_payment'); ?>
</button>
</td>
</tr>
<?php endforeach; if (empty($running_visits)): ?>
<tr><td colspan="5" class="text-center py-4 text-muted"><?php echo __('no_running_visits'); ?></td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Main Content Area -->
<div class="row">
<!-- Today's Appointments -->
<div class="col-lg-6 mb-4">
<div class="card shadow-sm h-100">
<div class="card-header py-3 d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-bold text-white"><i class="bi bi-calendar-event-fill me-2"></i> <?php echo __('today_appointments'); ?></h6>
<span class="badge bg-white text-primary"><?php echo count($appointments); ?></span>
</div>
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-hover mb-0">
<thead class="table-light sticky-top">
<tr>
<th><?php echo __('time'); ?></th>
<th><?php echo __('patient'); ?></th>
<th><?php echo __('doctor'); ?></th>
<th><?php echo __('status'); ?></th>
<th class="text-end"><?php echo __('actions'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($appointments as $a): ?>
<tr>
<td><?php echo date('H:i', strtotime($a['start_time'])); ?></td>
<td>
<div class="fw-bold"><?php echo htmlspecialchars($a['patient_name']); ?></div>
</td>
<td>
<?php
if ($a['doctor_name']) echo htmlspecialchars($a['doctor_name']);
elseif ($a['nurse_name']) echo htmlspecialchars($a['nurse_name']) . ' (' . __('nurse') . ')';
else echo '-';
?>
</td>
<td><span class="badge <?php echo $a['status'] === 'Completed' ? 'bg-success' : 'bg-secondary'; ?>"><?php echo __($a['status']); ?></span></td>
<td class="text-end">
<button class="btn btn-sm btn-outline-info" onclick="showReceptionistVisitModal(<?php echo $a['patient_id']; ?>)" title="<?php echo __('add_visit'); ?>">
<i class="bi bi-clipboard-plus"></i>
</button>
<button class="btn btn-sm btn-outline-primary" onclick='showDashboardEditModal(<?php echo json_encode($a, JSON_HEX_APOS | JSON_HEX_QUOT); ?>)' title="<?php echo __('edit'); ?>">
<i class="bi bi-pencil-square"></i>
</button>
</td>
</tr>
<?php endforeach; if (empty($appointments)): ?>
<tr><td colspan="5" class="text-center py-4 text-muted"><?php echo __('no_appointments_today'); ?></td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Patient List / Search -->
<div class="col-lg-6 mb-4">
<div class="card shadow-sm h-100">
<div class="card-header py-2">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-bold text-white"><i class="bi bi-people-fill me-2"></i> <?php echo __('patients'); ?></h6>
<div class="input-group input-group-sm" style="width: 200px;">
<input type="text" id="dashboardPatientSearch" class="form-control" placeholder="<?php echo __('search'); ?>...">
<button class="btn btn-light" type="button"><i class="bi bi-search"></i></button>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light sticky-top">
<tr>
<th><?php echo __('patient'); ?></th>
<th><?php echo __('phone'); ?></th>
<th><?php echo __('insurance'); ?></th>
<th class="text-end"><?php echo __('actions'); ?></th>
</tr>
</thead>
<tbody id="dashboardPatientsTable">
<?php foreach ($patients as $p): ?>
<tr>
<td><?php echo htmlspecialchars($p['name']); ?></td>
<td><?php echo calculate_age($p['dob']); ?></td>
<td>
<div class="fw-bold"><?php echo htmlspecialchars($p['name']); ?></div>
<small class="text-muted"><?php echo $p['dob']; ?> (<?php echo calculate_age($p['dob']); ?>)</small>
</td>
<td><?php echo htmlspecialchars($p['phone']); ?></td>
<td><span class="badge <?php echo $p['insurance_name'] ? 'bg-primary' : 'bg-secondary'; ?>"><?php echo $p['insurance_name'] ?: __('not_insured'); ?></span></td>
<td class="text-end">
<button class="btn btn-sm btn-outline-info" onclick="showReceptionistVisitModal(<?php echo $p['id']; ?>)" title="<?php echo __('add_visit'); ?>">
<i class="bi bi-clipboard-plus"></i>
</button>
<button class="btn btn-sm btn-outline-primary" onclick="showEditPatientModal(<?php echo htmlspecialchars(json_encode($p, JSON_UNESCAPED_UNICODE)); ?>)" title="<?php echo __('edit'); ?>">
<i class="bi bi-pencil-square"></i>
</button>
</td>
</tr>
<?php endforeach; if (empty($patients)): ?>
<tr><td colspan="4" class="text-center py-4 text-muted">No patients found.</td></tr>
@ -132,37 +328,572 @@ $appointments = $db->query($appointments_sql)->fetchAll();
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card shadow-sm h-100">
<div class="card-header py-3">
<h6 class="mb-0 fw-bold text-white"><i class="bi bi-calendar-event-fill me-2"></i> <?php echo __('appointments'); ?></h6>
</div>
<!-- Dashboard Appointment Edit Modal -->
<div class="modal fade" id="dashboardAppointmentModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold text-secondary" id="dashModalTitle">Edit Appointment</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th><?php echo __('patient'); ?></th>
<th><?php echo __('doctor'); ?></th>
<th><?php echo __('date'); ?></th>
<th><?php echo __('status'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($appointments as $a): ?>
<tr>
<td><?php echo htmlspecialchars($a['patient_name']); ?></td>
<td><?php echo htmlspecialchars($a['doctor_name']); ?></td>
<td><?php echo date('M d, H:i', strtotime($a['start_time'])); ?></td>
<td><span class="badge <?php echo $a['status'] === 'Completed' ? 'bg-success' : 'bg-secondary'; ?>"><?php echo __($a['status']); ?></span></td>
</tr>
<?php endforeach; if (empty($appointments)): ?>
<tr><td colspan="4" class="text-center py-4 text-muted">No appointments found.</td></tr>
<?php endif; ?>
</tbody>
</table>
<div class="modal-body pt-3">
<input type="hidden" id="dash_apt_id">
<div class="mb-3">
<label class="form-label small text-muted"><?php echo __('patient'); ?></label>
<select id="dash_apt_patient_id" class="form-select select2-modal-dash">
<?php foreach ($all_patients as $p): ?>
<option value="<?php echo $p['id']; ?>" data-address="<?php echo htmlspecialchars($p['address'] ?? ''); ?>"><?php echo htmlspecialchars($p['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label small text-muted">Visit Type</label>
<select id="dash_apt_visit_type" class="form-select" onchange="dashboard_toggleAddressField()">
<option value="Clinic">Clinic Visit</option>
<option value="Home">Home Visit</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label small text-muted">Provider Type</label>
<select id="dash_apt_provider_type" class="form-select" onchange="dashboard_toggleProviderField()">
<option value="Doctor">Doctor</option>
<option value="Nurse">Nurse</option>
</select>
</div>
</div>
<div class="mb-3 d-none" id="dash_div_address">
<label class="form-label small text-muted">Address (For Home Visit)</label>
<textarea id="dash_apt_address" class="form-control" rows="2" placeholder="Enter patient address..."></textarea>
</div>
<div class="mb-3" id="dash_div_doctor">
<label class="form-label small text-muted"><?php echo __('doctor'); ?></label>
<select id="dash_apt_doctor_id" class="form-select select2-modal-dash">
<option value="">Select Doctor</option>
<?php foreach ($all_doctors as $d): ?>
<option value="<?php echo $d['id']; ?>"><?php echo htmlspecialchars($d['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3 d-none" id="dash_div_nurse">
<label class="form-label small text-muted"><?php echo __('nurse'); ?></label>
<select id="dash_apt_nurse_id" class="form-select select2-modal-dash">
<option value="">Select Nurse</option>
<?php foreach ($all_nurses as $n): ?>
<option value="<?php echo $n['id']; ?>"><?php echo htmlspecialchars($n['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label small text-muted"><?php echo __('date'); ?> & <?php echo __('time'); ?></label>
<input type="datetime-local" id="dash_apt_start_time" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label class="form-label small text-muted"><?php echo __('status'); ?></label>
<select id="dash_apt_status" class="form-select">
<option value="Scheduled">Scheduled</option>
<option value="Completed">Completed</option>
<option value="Cancelled">Cancelled</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label small text-muted"><?php echo __('reason'); ?></label>
<textarea id="dash_apt_reason" class="form-control" rows="2"></textarea>
</div>
</div>
<div class="modal-footer border-0 bg-light rounded-bottom">
<div class="d-flex justify-content-between w-100">
<button type="button" class="btn btn-outline-danger shadow-sm" onclick="dashboard_deleteAppointment()">
<i class="bi bi-trash"></i> <?php echo __('delete'); ?>
</button>
<div>
<button type="button" class="btn btn-secondary shadow-sm me-2" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
<button type="button" class="btn btn-primary shadow-sm px-4" onclick="dashboard_saveAppointment()">
<i class="bi bi-check2-circle me-1"></i> <?php echo __('save'); ?>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Receptionist Quick Visit Modal -->
<div class="modal fade" id="receptionistVisitModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<form action="dashboard.php" method="POST">
<input type="hidden" name="action" value="record_visit">
<div class="modal-content border-0 shadow">
<div class="modal-header">
<h5 class="modal-title fw-bold text-secondary"><?php echo __('new_visit'); ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label"><?php echo __('patient'); ?></label>
<select name="patient_id" id="quick_visit_patient_id" class="form-select select2-modal-quick" required>
<option value=""><?php echo __('select'); ?>...</option>
<?php foreach ($all_patients as $p): ?>
<option value="<?php echo $p['id']; ?>">
<?php echo htmlspecialchars($p['name']) . ($p['phone'] ? ' - ' . htmlspecialchars($p['phone']) : '') . ($p['civil_id'] ? ' (' . htmlspecialchars($p['civil_id']) . ')' : ''); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label"><?php echo __('doctor'); ?></label>
<select name="doctor_id" id="quick_visit_doctor_id" class="form-select select2-modal-quick" required>
<option value=""><?php echo __('select'); ?>...</option>
<?php foreach ($all_doctors as $d): ?>
<option value="<?php echo $d['id']; ?>"><?php echo htmlspecialchars($d['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="quick_visit_token" name="generate_token" value="1" checked>
<label class="form-check-label" for="quick_visit_token"><?php echo __('issue_token'); ?></label>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
<button type="submit" class="btn btn-primary px-4"><?php echo __('save'); ?></button>
</div>
</div>
</form>
</div>
</div>
<!-- Checkout/Billing Modal -->
<div class="modal fade" id="checkoutModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold text-secondary"><?php echo __('checkout_payment'); ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body pt-3">
<input type="hidden" id="checkout_bill_id">
<div class="alert alert-light border d-flex justify-content-between align-items-center mb-3">
<div>
<div class="small text-muted"><?php echo __('patient'); ?></div>
<div class="fw-bold" id="checkout_patient_name">-</div>
</div>
<div class="text-end">
<div class="small text-muted"><?php echo __('insurance'); ?></div>
<div class="fw-bold text-primary" id="checkout_insurance_name">-</div>
</div>
</div>
<!-- Add Items -->
<div class="card bg-light border-0 mb-3">
<div class="card-body p-2">
<div class="row g-2">
<div class="col-md-4">
<select id="checkout_service_select" class="form-select form-select-sm select2-modal-checkout">
<option value=""><?php echo __('select_service'); ?>...</option>
<?php foreach ($all_services as $s): ?>
<option value="<?php echo $s['name']; ?>" data-price="<?php echo $s['price']; ?>"><?php echo htmlspecialchars($s['name']); ?> ($<?php echo $s['price']; ?>)</option>
<?php endforeach; ?>
<?php foreach ($all_tests as $t): ?>
<option value="<?php echo $t['name']; ?>" data-price="<?php echo $t['price']; ?>"><?php echo htmlspecialchars($t['name']); ?> ($<?php echo $t['price']; ?>)</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<input type="text" id="checkout_custom_item" class="form-control form-control-sm" placeholder="Custom Item Description">
</div>
<div class="col-md-2">
<input type="number" id="checkout_item_price" class="form-control form-control-sm" placeholder="Price">
</div>
<div class="col-md-2">
<button class="btn btn-primary btn-sm w-100" onclick="addBillItem()"><i class="bi bi-plus-lg"></i> Add</button>
</div>
</div>
</div>
</div>
<!-- Items Table -->
<div class="table-responsive mb-3">
<table class="table table-sm table-bordered">
<thead class="table-light">
<tr>
<th>Description</th>
<th class="text-end" width="100">Amount</th>
<th width="50"></th>
</tr>
</thead>
<tbody id="checkout_items_table">
<!-- Items go here -->
</tbody>
<tfoot class="table-light fw-bold">
<tr>
<td class="text-end">Total</td>
<td class="text-end" id="checkout_total_amount">$0.00</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
<!-- Payment Details -->
<div class="row g-3">
<div class="col-md-4">
<label class="form-label small text-muted">Insurance Coverage %</label>
<div class="input-group input-group-sm">
<input type="number" id="checkout_insurance_percent" class="form-control" value="0" min="0" max="100" onchange="calculateTotals()">
<span class="input-group-text">%</span>
</div>
</div>
<div class="col-md-4">
<label class="form-label small text-muted">Insurance Pays</label>
<div class="input-group input-group-sm">
<span class="input-group-text">$</span>
<input type="number" id="checkout_insurance_amount" class="form-control" readonly>
</div>
</div>
<div class="col-md-4">
<label class="form-label small text-muted fw-bold text-dark">Patient Pays</label>
<div class="input-group input-group-sm">
<span class="input-group-text">$</span>
<input type="number" id="checkout_patient_amount" class="form-control fw-bold" readonly>
</div>
</div>
</div>
<hr>
<div class="row g-3 align-items-end">
<div class="col-md-6">
<label class="form-label small text-muted">Payment Method</label>
<select id="checkout_payment_method" class="form-select">
<option value="Cash">Cash</option>
<option value="Card">Card</option>
<option value="Online">Online</option>
<option value="Insurance">Insurance Only</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label small text-muted">Notes</label>
<input type="text" id="checkout_notes" class="form-control" placeholder="Optional notes...">
</div>
</div>
</div>
<div class="modal-footer border-0 bg-light rounded-bottom">
<button type="button" class="btn btn-outline-dark me-auto" onclick="printBill()"><i class="bi bi-printer"></i> <?php echo __('print_bill'); ?></button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('close'); ?></button>
<button type="button" class="btn btn-success px-4" onclick="completePayment()">
<i class="bi bi-check2-circle me-1"></i> Pay & Close Visit
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('dashboardPatientSearch');
const tableBody = document.getElementById('dashboardPatientsTable');
let timeout = null;
searchInput.addEventListener('input', function() {
clearTimeout(timeout);
timeout = setTimeout(function() {
const query = searchInput.value;
fetch('dashboard.php?ajax_search=1&search=' + encodeURIComponent(query))
.then(response => response.json())
.then(data => {
tableBody.innerHTML = data.html;
})
.catch(error => console.error('Error:', error));
}, 300);
});
// Initialize Select2
$('#dashboardAppointmentModal').on('shown.bs.modal', function() {
$('.select2-modal-dash').each(function() {
$(this).select2({ dropdownParent: $('#dashboardAppointmentModal'), theme: 'bootstrap-5', width: '100%' });
});
});
$('#receptionistVisitModal').on('shown.bs.modal', function() {
$('.select2-modal-quick').each(function() {
$(this).select2({ dropdownParent: $('#receptionistVisitModal'), theme: 'bootstrap-5', width: '100%' });
});
});
$('#checkoutModal').on('shown.bs.modal', function() {
$('.select2-modal-checkout').each(function() {
$(this).select2({ dropdownParent: $('#checkoutModal'), theme: 'bootstrap-5', width: '100%' });
// Helper for filling inputs on selection
$(this).on('select2:select', function(e) {
var data = e.params.data;
// Since select2 data doesn't keep data attributes easily without extra work,
// we'll access the underlying option
var element = $(this).find('option:selected');
var price = element.data('price');
var text = element.text();
if (price) {
document.getElementById('checkout_custom_item').value = text.split(' ($')[0];
document.getElementById('checkout_item_price').value = price;
}
});
});
});
// Auto-fill address
$('#dash_apt_patient_id').on('change', function() {
if (document.getElementById('dash_apt_visit_type').value === 'Home') {
var addressField = document.getElementById('dash_apt_address');
if (!addressField.value) {
var selectedOption = this.options[this.selectedIndex];
if (selectedOption && selectedOption.dataset.address) {
addressField.value = selectedOption.dataset.address;
}
}
}
});
});
// ... existing functions ...
function dashboard_toggleAddressField() {
var type = document.getElementById('dash_apt_visit_type').value;
var div = document.getElementById('dash_div_address');
if (type === 'Home') div.classList.remove('d-none'); else div.classList.add('d-none');
}
function dashboard_toggleProviderField() {
var type = document.getElementById('dash_apt_provider_type').value;
var divDoc = document.getElementById('dash_div_doctor');
var divNurse = document.getElementById('dash_div_nurse');
if (type === 'Nurse') { divDoc.classList.add('d-none'); divNurse.classList.remove('d-none'); }
else { divDoc.classList.remove('d-none'); divNurse.classList.add('d-none'); }
}
function showDashboardEditModal(data) {
document.getElementById('dash_apt_id').value = data.id;
$('#dash_apt_patient_id').val(data.patient_id).trigger('change');
document.getElementById('dash_apt_visit_type').value = data.visit_type || 'Clinic';
document.getElementById('dash_apt_address').value = data.address || '';
dashboard_toggleAddressField();
var providerType = data.nurse_id ? 'Nurse' : 'Doctor';
document.getElementById('dash_apt_provider_type').value = providerType;
dashboard_toggleProviderField();
if (providerType === 'Doctor') $('#dash_apt_doctor_id').val(data.doctor_id).trigger('change');
else $('#dash_apt_nurse_id').val(data.nurse_id).trigger('change');
var start = new Date(data.start_time);
if (!isNaN(start)) {
var offset = start.getTimezoneOffset() * 60000;
document.getElementById('dash_apt_start_time').value = (new Date(start.getTime() - offset)).toISOString().slice(0, 16);
} else {
document.getElementById('dash_apt_start_time').value = data.start_time.replace(' ', 'T').slice(0, 16);
}
document.getElementById('dash_apt_status').value = data.status;
document.getElementById('dash_apt_reason').value = data.reason || '';
new bootstrap.Modal(document.getElementById('dashboardAppointmentModal')).show();
}
function showReceptionistVisitModal(patientId) {
$('#quick_visit_patient_id').val(patientId || '').trigger('change');
$('#quick_visit_doctor_id').val('').trigger('change');
new bootstrap.Modal(document.getElementById('receptionistVisitModal')).show();
}
function dashboard_saveAppointment() {
var id = document.getElementById('dash_apt_id').value;
var providerType = document.getElementById('dash_apt_provider_type').value;
var data = {
action: 'update',
id: id,
patient_id: document.getElementById('dash_apt_patient_id').value,
doctor_id: providerType === 'Doctor' ? document.getElementById('dash_apt_doctor_id').value : null,
nurse_id: providerType === 'Nurse' ? document.getElementById('dash_apt_nurse_id').value : null,
visit_type: document.getElementById('dash_apt_visit_type').value,
address: document.getElementById('dash_apt_address').value,
start_time: document.getElementById('dash_apt_start_time').value,
status: document.getElementById('dash_apt_status').value,
reason: document.getElementById('dash_apt_reason').value
};
fetch('api/appointments.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) })
.then(r => r.json()).then(res => { if(res.success) window.location.reload(); else alert(res.error); });
}
function dashboard_deleteAppointment() {
var id = document.getElementById('dash_apt_id').value;
if (id && confirm('Delete?')) {
fetch('api/appointments.php', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({action:'delete', id:id}) })
.then(r => r.json()).then(res => { if(res.success) window.location.reload(); else alert(res.error); });
}
}
// --- BILLING FUNCTIONS ---
let currentBillId = 0;
let currentTotal = 0;
function fetchBillDetails(id, type = 'visit') {
const param = type === 'visit' ? `visit_id=${id}` : `bill_id=${id}`;
return fetch(`api/billing.php?action=get_bill_details&${param}`)
.then(r => r.json());
}
function showCheckoutModal(visitId) {
fetchBillDetails(visitId, 'visit')
.then(data => {
if (!data.success) { alert(data.error); return; }
updateCheckoutModalUI(data);
new bootstrap.Modal(document.getElementById('checkoutModal')).show();
});
}
function updateCheckoutModalUI(data) {
currentBillId = data.bill.id;
document.getElementById('checkout_bill_id').value = currentBillId;
document.getElementById('checkout_patient_name').innerText = data.visit.patient_name;
document.getElementById('checkout_insurance_name').innerText = data.insurance_name || 'None';
// Auto-set generic insurance percent if insured (e.g., 80% coverage)
// Only set if value is 0 (initial load) to avoid overwriting user input on refresh
if (data.has_insurance && data.bill.status === 'Pending' && parseFloat(data.bill.insurance_covered) === 0) {
// Do not force overwrite if user might have changed it, but for now kept simple
// document.getElementById('checkout_insurance_percent').value = 0;
}
renderBillItems(data.items);
}
function renderBillItems(items) {
const tbody = document.getElementById('checkout_items_table');
tbody.innerHTML = '';
let total = 0;
items.forEach(item => {
total += parseFloat(item.amount);
tbody.innerHTML += `
<tr>
<td>${item.description}</td>
<td class="text-end">$${parseFloat(item.amount).toFixed(2)}</td>
<td class="text-center">
<button class="btn btn-sm btn-link text-danger p-0" onclick="removeBillItem(${item.id})"><i class="bi bi-x-lg"></i></button>
</td>
</tr>
`;
});
currentTotal = total;
document.getElementById('checkout_total_amount').innerText = '$' + total.toFixed(2);
calculateTotals();
}
function calculateTotals() {
const percent = parseFloat(document.getElementById('checkout_insurance_percent').value) || 0;
const insuranceAmount = (currentTotal * percent) / 100;
const patientAmount = currentTotal - insuranceAmount;
document.getElementById('checkout_insurance_amount').value = insuranceAmount.toFixed(2);
document.getElementById('checkout_patient_amount').value = patientAmount.toFixed(2);
}
function addBillItem() {
const desc = document.getElementById('checkout_custom_item').value;
const price = document.getElementById('checkout_item_price').value;
if (!desc || !price) { alert('Please enter description and price'); return; }
const formData = new FormData();
formData.append('action', 'add_item');
formData.append('bill_id', currentBillId);
formData.append('description', desc);
formData.append('amount', price);
fetch('api/billing.php', { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
refreshBillItems();
// Clear inputs
document.getElementById('checkout_custom_item').value = '';
document.getElementById('checkout_item_price').value = '';
$('#checkout_service_select').val('').trigger('change'); // Reset Select2
} else { alert(data.error); }
});
}
function removeBillItem(itemId) {
if (!confirm('Remove item?')) return;
const formData = new FormData();
formData.append('action', 'remove_item');
formData.append('item_id', itemId);
fetch('api/billing.php', { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) refreshBillItems();
else alert(data.error);
});
}
function refreshBillItems() {
fetchBillDetails(currentBillId, 'bill')
.then(data => {
if (data.success) {
updateCheckoutModalUI(data);
} else {
alert(data.error);
}
});
}
function completePayment() {
const formData = new FormData();
formData.append('action', 'complete_payment');
formData.append('bill_id', currentBillId);
formData.append('payment_method', document.getElementById('checkout_payment_method').value);
formData.append('notes', document.getElementById('checkout_notes').value);
const insurance = document.getElementById('checkout_insurance_amount').value;
const patient = document.getElementById('checkout_patient_amount').value;
const updateData = new FormData();
updateData.append('action', 'update_totals');
updateData.append('bill_id', currentBillId);
updateData.append('insurance_covered', insurance);
updateData.append('patient_payable', patient);
fetch('api/billing.php', { method: 'POST', body: updateData })
.then(() => {
return fetch('api/billing.php', { method: 'POST', body: formData });
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert('Payment Recorded!');
window.location.reload();
} else { alert(data.error); }
});
}
function printBill() {
if (currentBillId) {
window.open('print_bill.php?bill_id=' + currentBillId, '_blank');
}
}
</script>

View File

@ -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' => 'فاتورة',
]
];

7
list_tables.php Normal file
View File

@ -0,0 +1,7 @@
<?php
require_once 'db/config.php';
$db = db();
$stmt = $db->query("SHOW TABLES");
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
echo implode("\n", $tables);

204
print_bill.php Normal file
View File

@ -0,0 +1,204 @@
<?php
require 'db/config.php';
require 'helpers.php';
// Enable error reporting for debugging
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
try {
$db = db();
$bill_id = $_GET['bill_id'] ?? 0;
if (!$bill_id) {
throw new Exception("Invalid Bill ID");
}
// Fetch Bill Details
$stmt = $db->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());
}
?>
<!DOCTYPE html>
<html lang="<?php echo $lang; ?>" dir="<?php echo $lang == 'ar' ? 'rtl' : 'ltr'; ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invoice #<?php echo $bill_id; ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { font-family: 'Times New Roman', Times, serif; color: #333; }
.invoice-header { border-bottom: 2px solid #ddd; padding-bottom: 20px; margin-bottom: 30px; }
.invoice-footer { border-top: 2px solid #ddd; padding-top: 20px; margin-top: 50px; }
.company-logo { max-height: 80px; }
.invoice-title { font-size: 2rem; color: #555; text-transform: uppercase; letter-spacing: 2px; }
.table thead th { border-bottom: 2px solid #333; background-color: #f8f9fa; }
.table-bordered td, .table-bordered th { border-color: #dee2e6; }
@media print {
.no-print { display: none !important; }
body { padding: 20px; background: white; }
.card { border: none !important; box-shadow: none !important; }
}
</style>
</head>
<body onload="window.print()">
<div class="container my-5">
<div class="no-print mb-4 text-end">
<button onclick="window.print()" class="btn btn-primary"><i class="bi bi-printer"></i> Print</button>
<button onclick="window.close()" class="btn btn-secondary">Close</button>
</div>
<div class="card p-4">
<!-- Header -->
<div class="invoice-header">
<div class="row align-items-center">
<div class="col-6">
<?php if (!empty($settings['company_logo'])): ?>
<img src="<?php echo htmlspecialchars($settings['company_logo']); ?>" alt="Logo" class="company-logo mb-2">
<?php else: ?>
<h2 class="fw-bold m-0"><?php echo htmlspecialchars($settings['company_name'] ?? 'Hospital Name'); ?></h2>
<?php endif; ?>
<div class="small text-muted">
<?php echo htmlspecialchars($settings['company_address'] ?? '123 Medical Center St.'); ?><br>
Phone: <?php echo htmlspecialchars($settings['company_phone'] ?? '+123 456 7890'); ?><br>
Email: <?php echo htmlspecialchars($settings['company_email'] ?? 'info@hospital.com'); ?>
</div>
</div>
<div class="col-6 text-end">
<h1 class="invoice-title"><?php echo __('invoice'); ?></h1>
<p class="lead mb-0">#INV-<?php echo str_pad($bill_id, 6, '0', STR_PAD_LEFT); ?></p>
<p class="text-muted small">Date: <?php echo date('d M Y', strtotime($bill['created_at'])); ?></p>
</div>
</div>
</div>
<!-- Patient & Visit Details -->
<div class="row mb-4">
<div class="col-6">
<h6 class="text-uppercase text-muted small fw-bold mb-2">Bill To:</h6>
<h5 class="fw-bold mb-1"><?php echo htmlspecialchars($visit['patient_name']); ?></h5>
<p class="mb-0 text-muted">
<?php if ($visit['patient_phone']) echo 'Phone: ' . htmlspecialchars($visit['patient_phone']) . '<br>'; ?>
<?php if ($visit['civil_id']) echo 'ID: ' . htmlspecialchars($visit['civil_id']); ?>
</p>
</div>
<div class="col-6 text-end">
<h6 class="text-uppercase text-muted small fw-bold mb-2">Visit Details:</h6>
<p class="mb-0">
<strong>Doctor:</strong> <?php echo htmlspecialchars($visit['doctor_name_' . $lang] ?? $visit['doctor_name_en']); ?><br>
<strong>Visit ID:</strong> #<?php echo $visit['id']; ?><br>
<strong>Status:</strong> <span class="badge bg-light text-dark border"><?php echo $bill['status']; ?></span>
</p>
</div>
</div>
<!-- Items Table -->
<table class="table table-bordered mb-4">
<thead>
<tr>
<th class="text-center" width="50">#</th>
<th><?php echo __('description'); ?></th>
<th class="text-end" width="150"><?php echo __('amount'); ?></th>
</tr>
</thead>
<tbody>
<?php $i = 1; foreach ($items as $item): ?>
<tr>
<td class="text-center"><?php echo $i++; ?></td>
<td><?php echo htmlspecialchars($item['description']); ?></td>
<td class="text-end">$<?php echo number_format($item['amount'], 2); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td colspan="2" class="text-end fw-bold">Total Amount</td>
<td class="text-end fw-bold">$<?php echo number_format($bill['total_amount'], 2); ?></td>
</tr>
<?php if ($bill['insurance_covered'] > 0): ?>
<tr>
<td colspan="2" class="text-end text-success">Insurance Covered</td>
<td class="text-end text-success">-$<?php echo number_format($bill['insurance_covered'], 2); ?></td>
</tr>
<?php endif; ?>
<tr>
<td colspan="2" class="text-end fw-bold fs-5">Patient Due</td>
<td class="text-end fw-bold fs-5">$<?php echo number_format($bill['patient_payable'], 2); ?></td>
</tr>
</tfoot>
</table>
<!-- Payment Info -->
<div class="row">
<div class="col-md-6">
<?php if ($bill['status'] == 'Paid'): ?>
<div class="alert alert-success d-inline-block py-2 px-3">
<i class="bi bi-check-circle-fill me-1"></i> <strong>PAID</strong> via <?php echo htmlspecialchars($bill['payment_method']); ?>
</div>
<?php else: ?>
<div class="alert alert-warning d-inline-block py-2 px-3">
<i class="bi bi-clock me-1"></i> <strong>PENDING</strong>
</div>
<?php endif; ?>
<?php if (!empty($bill['notes'])): ?>
<p class="text-muted small mt-2"><strong>Notes:</strong> <?php echo htmlspecialchars($bill['notes']); ?></p>
<?php endif; ?>
</div>
<div class="col-md-6 text-end">
<!-- Signature or Thank you -->
<br><br>
<p class="fw-bold mb-0">_________________________</p>
<p class="small text-muted">Authorized Signature</p>
</div>
</div>
<!-- Footer -->
<div class="invoice-footer text-center small text-muted">
<p class="mb-1">Thank you for your visit!</p>
<p>Generated on <?php echo date('Y-m-d H:i:s'); ?></p>
</div>
</div>
</div>
</body>
</html>