434 lines
23 KiB
HTML
434 lines
23 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Payments - {{ block.super }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">Payments</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'billing:dashboard' %}">Billing</a></li>
|
|
<li class="breadcrumb-item active">Payments</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<a href="{% url 'billing:payment_create' %}" class="btn btn-success">
|
|
<i class="fas fa-plus me-2"></i>Record Payment
|
|
</a>
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
|
<span class="visually-hidden">Toggle Dropdown</span>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="">
|
|
<i class="fas fa-download me-2"></i>Export Payments
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="window.print()">
|
|
<i class="fas fa-print me-2"></i>Print List
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
{# <div class="row mb-4" hx-get="{% url 'billing:payment_stats' %}" hx-trigger="load, every 60s">#}
|
|
{# <div class="col-md-3 mb-3">#}
|
|
{# <div class="card bg-gradient-success text-white">#}
|
|
{# <div class="card-body">#}
|
|
{# <div class="d-flex justify-content-between align-items-center">#}
|
|
{# <div>#}
|
|
{# <h6 class="card-title mb-1 opacity-75">Total Payments</h6>#}
|
|
{# <h3 class="mb-0">{{ stats.total_payments|default:0 }}</h3>#}
|
|
{# </div>#}
|
|
{# <div class="text-white-50">#}
|
|
{# <i class="fas fa-credit-card fa-2x"></i>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# <div class="col-md-3 mb-3">#}
|
|
{# <div class="card bg-gradient-primary text-white">#}
|
|
{# <div class="card-body">#}
|
|
{# <div class="d-flex justify-content-between align-items-center">#}
|
|
{# <div>#}
|
|
{# <h6 class="card-title mb-1 opacity-75">Total Amount</h6>#}
|
|
{# <h3 class="mb-0">${{ stats.total_amount|default:0|floatformat:2 }}</h3>#}
|
|
{# </div>#}
|
|
{# <div class="text-white-50">#}
|
|
{# <i class="fas fa-dollar-sign fa-2x"></i>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# <div class="col-md-3 mb-3">#}
|
|
{# <div class="card bg-gradient-info text-white">#}
|
|
{# <div class="card-body">#}
|
|
{# <div class="d-flex justify-content-between align-items-center">#}
|
|
{# <div>#}
|
|
{# <h6 class="card-title mb-1 opacity-75">Today's Payments</h6>#}
|
|
{# <h3 class="mb-0">${{ stats.today_amount|default:0|floatformat:2 }}</h3>#}
|
|
{# </div>#}
|
|
{# <div class="text-white-50">#}
|
|
{# <i class="fas fa-calendar-day fa-2x"></i>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# <div class="col-md-3 mb-3">#}
|
|
{# <div class="card bg-gradient-warning text-white">#}
|
|
{# <div class="card-body">#}
|
|
{# <div class="d-flex justify-content-between align-items-center">#}
|
|
{# <div>#}
|
|
{# <h6 class="card-title mb-1 opacity-75">Pending</h6>#}
|
|
{# <h3 class="mb-0">{{ stats.pending_count|default:0 }}</h3>#}
|
|
{# </div>#}
|
|
{# <div class="text-white-50">#}
|
|
{# <i class="fas fa-clock fa-2x"></i>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
{# </div>#}
|
|
|
|
<!-- Search and Filter Card -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-search me-2"></i>Search & Filter
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="get" class="row g-3" hx-get="{% url 'billing:payment_list' %}" hx-target="#payment-list-container" hx-trigger="submit, change delay:500ms">
|
|
<div class="col-md-3">
|
|
<div class="form-floating">
|
|
<input type="text" class="form-control" id="search" name="search" value="{{ request.GET.search }}" placeholder="Search payments...">
|
|
<label for="search">Search Payments</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-floating">
|
|
<select class="form-select" id="payment_method" name="payment_method">
|
|
<option value="">All Methods</option>
|
|
<option value="CASH" {% if request.GET.payment_method == 'CASH' %}selected{% endif %}>Cash</option>
|
|
<option value="CHECK" {% if request.GET.payment_method == 'CHECK' %}selected{% endif %}>Check</option>
|
|
<option value="CREDIT_CARD" {% if request.GET.payment_method == 'CREDIT_CARD' %}selected{% endif %}>Credit Card</option>
|
|
<option value="DEBIT_CARD" {% if request.GET.payment_method == 'DEBIT_CARD' %}selected{% endif %}>Debit Card</option>
|
|
<option value="BANK_TRANSFER" {% if request.GET.payment_method == 'BANK_TRANSFER' %}selected{% endif %}>Bank Transfer</option>
|
|
<option value="INSURANCE" {% if request.GET.payment_method == 'INSURANCE' %}selected{% endif %}>Insurance</option>
|
|
<option value="OTHER" {% if request.GET.payment_method == 'OTHER' %}selected{% endif %}>Other</option>
|
|
</select>
|
|
<label for="payment_method">Payment Method</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-floating">
|
|
<select class="form-select" id="status" name="status">
|
|
<option value="">All Statuses</option>
|
|
<option value="PENDING" {% if request.GET.status == 'PENDING' %}selected{% endif %}>Pending</option>
|
|
<option value="PROCESSING" {% if request.GET.status == 'PROCESSING' %}selected{% endif %}>Processing</option>
|
|
<option value="COMPLETED" {% if request.GET.status == 'COMPLETED' %}selected{% endif %}>Completed</option>
|
|
<option value="FAILED" {% if request.GET.status == 'FAILED' %}selected{% endif %}>Failed</option>
|
|
<option value="REFUNDED" {% if request.GET.status == 'REFUNDED' %}selected{% endif %}>Refunded</option>
|
|
</select>
|
|
<label for="status">Status</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-floating">
|
|
<input type="date" class="form-control" id="date_from" name="date_from" value="{{ request.GET.date_from }}">
|
|
<label for="date_from">From Date</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="form-floating">
|
|
<input type="date" class="form-control" id="date_to" name="date_to" value="{{ request.GET.date_to }}">
|
|
<label for="date_to">To Date</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-1">
|
|
<button type="submit" class="btn btn-primary h-100 w-100">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Payments List Card -->
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-credit-card me-2"></i>Payments
|
|
<span class="badge bg-secondary ms-2">{{ page_obj.paginator.count }} total</span>
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="selectAll()">
|
|
<i class="fas fa-check-square me-1"></i>Select All
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="clearSelection()">
|
|
<i class="fas fa-square me-1"></i>Clear
|
|
</button>
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-cog me-1"></i>Bulk Actions
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" onclick="bulkAction('export')">
|
|
<i class="fas fa-download me-2"></i>Export Selected
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="bulkAction('print')">
|
|
<i class="fas fa-print me-2"></i>Print Selected
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div id="payment-list-container">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th width="40">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()">
|
|
</div>
|
|
</th>
|
|
<th>Payment Number</th>
|
|
<th>Patient</th>
|
|
<th>Bill Number</th>
|
|
<th>Payment Date</th>
|
|
<th>Amount</th>
|
|
<th>Method</th>
|
|
<th>Status</th>
|
|
<th>Reference</th>
|
|
<th width="120">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for payment in page_obj %}
|
|
<tr>
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input payment-checkbox" type="checkbox" value="{{ payment.payment_id }}">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<a href="{% url 'billing:payment_detail' payment.payment_id %}" class="text-decoration-none fw-bold">
|
|
{{ payment.payment_number }}
|
|
</a>
|
|
</td>
|
|
<td>
|
|
<div>
|
|
<strong>{{ payment.medical_bill.patient.get_full_name }}</strong>
|
|
<br><small class="text-muted">MRN: {{ payment.medical_bill.patient.mrn }}</small>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<a href="{% url 'billing:bill_detail' payment.medical_bill.bill_id %}" class="text-decoration-none">
|
|
{{ payment.medical_bill.bill_number }}
|
|
</a>
|
|
</td>
|
|
<td>{{ payment.payment_date|date:"M d, Y" }}</td>
|
|
<td class="text-end fw-bold text-success">${{ payment.payment_amount|floatformat:2 }}</td>
|
|
<td>
|
|
<span class="badge bg-light text-dark">
|
|
{{ payment.get_payment_method_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if payment.status == 'PENDING' %}
|
|
<span class="badge bg-warning">Pending</span>
|
|
{% elif payment.status == 'PROCESSING' %}
|
|
<span class="badge bg-info">Processing</span>
|
|
{% elif payment.status == 'COMPLETED' %}
|
|
<span class="badge bg-success">Completed</span>
|
|
{% elif payment.status == 'FAILED' %}
|
|
<span class="badge bg-danger">Failed</span>
|
|
{% elif payment.status == 'REFUNDED' %}
|
|
<span class="badge bg-secondary">Refunded</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if payment.reference_number %}
|
|
<small class="text-muted">{{ payment.reference_number }}</small>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'billing:payment_detail' payment.payment_id %}" class="btn btn-outline-primary btn-sm" title="View Details">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
{% if payment.status == 'PENDING' %}
|
|
<a href="{% url 'billing:payment_update' payment.payment_id %}" class="btn btn-outline-secondary btn-sm" title="Edit">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
{% endif %}
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
|
|
<span class="visually-hidden">Toggle Dropdown</span>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="{% url 'billing:payment_receipt' payment.payment_id %}">
|
|
<i class="fas fa-receipt me-2"></i>Print Receipt
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'billing:payment_email' payment.payment_id %}">
|
|
<i class="fas fa-envelope me-2"></i>Email Receipt
|
|
</a></li>
|
|
{% if payment.status == 'COMPLETED' %}
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-warning" href="{% url 'billing:payment_refund' payment.payment_id %}">
|
|
<i class="fas fa-undo me-2"></i>Process Refund
|
|
</a></li>
|
|
{% endif %}
|
|
{% if payment.status == 'PENDING' %}
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-danger" href="{% url 'billing:payment_delete' payment.payment_id %}">
|
|
<i class="fas fa-trash me-2"></i>Delete
|
|
</a></li>
|
|
{% endif %}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="10" class="text-center py-5">
|
|
<div class="text-muted">
|
|
<i class="fas fa-credit-card fa-3x mb-3 opacity-50"></i>
|
|
<h5>No payments found</h5>
|
|
<p>No payments match your current filters.</p>
|
|
<a href="{% url 'billing:payment_create' %}" class="btn btn-success">
|
|
<i class="fas fa-plus me-2"></i>Record First Payment
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if page_obj.has_other_pages %}
|
|
<div class="card-footer">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="text-muted">
|
|
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} payments
|
|
</div>
|
|
<nav aria-label="Payments pagination">
|
|
<ul class="pagination pagination-sm mb-0">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page=1">First</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.previous_page_number }}">Previous</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ num }}</span>
|
|
</li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ num }}">{{ num }}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.next_page_number }}">Next</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?{% for key, value in request.GET.items %}{% if key != 'page' %}{{ key }}={{ value }}&{% endif %}{% endfor %}page={{ page_obj.paginator.num_pages }}">Last</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Bulk selection functionality
|
|
function toggleSelectAll() {
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
const paymentCheckboxes = document.querySelectorAll('.payment-checkbox');
|
|
|
|
paymentCheckboxes.forEach(checkbox => {
|
|
checkbox.checked = selectAllCheckbox.checked;
|
|
});
|
|
}
|
|
|
|
function selectAll() {
|
|
const paymentCheckboxes = document.querySelectorAll('.payment-checkbox');
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
|
|
paymentCheckboxes.forEach(checkbox => {
|
|
checkbox.checked = true;
|
|
});
|
|
selectAllCheckbox.checked = true;
|
|
}
|
|
|
|
function clearSelection() {
|
|
const paymentCheckboxes = document.querySelectorAll('.payment-checkbox');
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
|
|
paymentCheckboxes.forEach(checkbox => {
|
|
checkbox.checked = false;
|
|
});
|
|
selectAllCheckbox.checked = false;
|
|
}
|
|
|
|
{#function bulkAction(action) {#}
|
|
{# const selectedPayments = Array.from(document.querySelectorAll('.payment-checkbox:checked')).map(cb => cb.value);#}
|
|
{# #}
|
|
{# if (selectedPayments.length === 0) {#}
|
|
{# alert('Please select at least one payment.');#}
|
|
{# return;#}
|
|
{# }#}
|
|
{# #}
|
|
{# switch(action) {#}
|
|
{# case 'export':#}
|
|
{# window.location.href = `{% url 'billing:export_payments' %}?payments=${selectedPayments.join(',')}`;#}
|
|
{# break;#}
|
|
{# case 'print':#}
|
|
{# window.open(`{% url 'billing:print_payments' %}?payments=${selectedPayments.join(',')}`, '_blank');#}
|
|
{# break;#}
|
|
{# }#}
|
|
{# }#}
|
|
|
|
// Update select all checkbox based on individual selections
|
|
document.addEventListener('change', function(e) {
|
|
if (e.target.classList.contains('payment-checkbox')) {
|
|
const paymentCheckboxes = document.querySelectorAll('.payment-checkbox');
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
const checkedCount = document.querySelectorAll('.payment-checkbox:checked').length;
|
|
|
|
selectAllCheckbox.checked = checkedCount === paymentCheckboxes.length;
|
|
selectAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < paymentCheckboxes.length;
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|