399 lines
17 KiB
HTML
399 lines
17 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Medical Bills - {{ 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">Medical Bills</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">Medical Bills</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<a href="{% url 'billing:bill_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-2"></i>Create Bill
|
|
</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>
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li>
|
|
<a class="dropdown-item" href="{% url 'billing:export_bills' %}">
|
|
<i class="fas fa-download me-2"></i>Export Bills
|
|
</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:billing_stats' %}" hx-trigger="load, every 60s">
|
|
<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 Bills</h6>
|
|
<h3 class="mb-0">{{ stats.total_bills|default:0 }}</h3>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-file-invoice fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<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 Amount</h6>
|
|
<h3 class="mb-0"><span class="symbol">ê</span>{{ stats.total_amount|default:0|floatformat:'2g' }}</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">Total Paid</h6>
|
|
<h3 class="mb-0"><span class="symbol">ê</span>{{ stats.total_paid|floatformat:'2g' }}</h3>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-check-circle 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">Outstanding</h6>
|
|
<h3 class="mb-0"><span class="symbol">ê</span>{{ stats.outstanding_amount|floatformat:'2g' }}</h3>
|
|
</div>
|
|
<div class="text-white-50">
|
|
<i class="fas fa-exclamation-triangle fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search & Filter (Panel) -->
|
|
<div class="panel panel-inverse mb-4">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title"><i class="fas fa-search me-2"></i>Search & Filter</h4>
|
|
<div class="panel-heading-btn">
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<form method="get"
|
|
class="row g-3"
|
|
hx-get="{% url 'billing:bill_list' %}"
|
|
hx-target="#bill-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 bills...">
|
|
<label for="search">Search Bills</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="DRAFT" {% if request.GET.status == 'DRAFT' %}selected{% endif %}>Draft</option>
|
|
<option value="PENDING" {% if request.GET.status == 'PENDING' %}selected{% endif %}>Pending</option>
|
|
<option value="SUBMITTED" {% if request.GET.status == 'SUBMITTED' %}selected{% endif %}>Submitted</option>
|
|
<option value="PARTIAL_PAID" {% if request.GET.status == 'PARTIAL_PAID' %}selected{% endif %}>Partially Paid</option>
|
|
<option value="PAID" {% if request.GET.status == 'PAID' %}selected{% endif %}>Paid</option>
|
|
<option value="OVERDUE" {% if request.GET.status == 'OVERDUE' %}selected{% endif %}>Overdue</option>
|
|
<option value="COLLECTIONS" {% if request.GET.status == 'COLLECTIONS' %}selected{% endif %}>Collections</option>
|
|
<option value="WRITTEN_OFF" {% if request.GET.status == 'WRITTEN_OFF' %}selected{% endif %}>Written Off</option>
|
|
<option value="CANCELLED" {% if request.GET.status == 'CANCELLED' %}selected{% endif %}>Cancelled</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-2">
|
|
<div class="form-floating">
|
|
<input type="number" class="form-control" id="min_amount" name="min_amount" value="{{ request.GET.min_amount }}" step="0.01" placeholder="Min Amount">
|
|
<label for="min_amount">Min Amount</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>
|
|
|
|
<!-- Bills List (Panel) -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading d-flex justify-content-between align-items-center">
|
|
<h4 class="panel-title">
|
|
<i class="fas fa-file-invoice me-2"></i>Medical Bills
|
|
<span class="badge bg-secondary ms-2">{{ page_obj.paginator.count }} total</span>
|
|
</h4>
|
|
<div class="panel-heading-btn d-flex gap-2">
|
|
<button type="button" class="btn btn-xs btn-outline-secondary" onclick="selectAll()">
|
|
<i class="fas fa-check-square me-1"></i>Select All
|
|
</button>
|
|
<button type="button" class="btn btn-xs btn-outline-secondary" onclick="clearSelection()">
|
|
<i class="fas fa-square me-1"></i>Clear
|
|
</button>
|
|
<div class="btn-group btn-group-xs" 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>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-warning" href="#" onclick="bulkAction('submit')">
|
|
<i class="fas fa-paper-plane me-2"></i>Submit Selected
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body p-0">
|
|
<div id="bill-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>Bill Number</th>
|
|
<th>Patient</th>
|
|
<th>Date</th>
|
|
<th class="text-end">Total Amount</th>
|
|
<th class="text-end">Paid Amount</th>
|
|
<th class="text-end">Balance</th>
|
|
<th>Status</th>
|
|
<th>Due Date</th>
|
|
<th width="120">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for bill in page_obj %}
|
|
<tr>
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input bill-checkbox" type="checkbox" value="{{ bill.bill_id }}">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<a href="{% url 'billing:bill_detail' bill.bill_id %}" class="text-decoration-none fw-bold">
|
|
{{ bill.bill_number }}
|
|
</a>
|
|
</td>
|
|
<td>
|
|
<div>
|
|
<strong>{{ bill.patient.get_full_name }}</strong>
|
|
<br><small class="text-muted">MRN: {{ bill.patient.mrn }}</small>
|
|
</div>
|
|
</td>
|
|
<td>{{ bill.bill_date|date:"M d, Y" }}</td>
|
|
<td class="text-end"><span class="symbol">ê</span>{{ bill.total_amount|floatformat:'2g' }}</td>
|
|
<td class="text-end"><span class="symbol">ê</span>{{ bill.paid_amount|floatformat:'2g' }}</td>
|
|
<td class="text-end">
|
|
<span class="{% if bill.balance_amount > 0 %}text-danger fw-bold{% else %}text-success{% endif %}">
|
|
<span class="symbol">ê</span>{{ bill.balance_amount|floatformat:'2g' }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if bill.status == 'DRAFT' %}
|
|
<span class="badge bg-secondary">Draft</span>
|
|
{% elif bill.status == 'PENDING' %}
|
|
<span class="badge bg-warning">Pending</span>
|
|
{% elif bill.status == 'SUBMITTED' %}
|
|
<span class="badge bg-info">Submitted</span>
|
|
{% elif bill.status == 'PARTIAL_PAID' %}
|
|
<span class="badge bg-primary">Partially Paid</span>
|
|
{% elif bill.status == 'PAID' %}
|
|
<span class="badge bg-success">Paid</span>
|
|
{% elif bill.status == 'OVERDUE' %}
|
|
<span class="badge bg-danger">Overdue</span>
|
|
{% elif bill.status == 'COLLECTIONS' %}
|
|
<span class="badge bg-dark">Collections</span>
|
|
{% elif bill.status == 'WRITTEN_OFF' %}
|
|
<span class="badge bg-secondary">Written Off</span>
|
|
{% elif bill.status == 'CANCELLED' %}
|
|
<span class="badge bg-secondary">Cancelled</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if bill.due_date %}
|
|
<span class="{% if bill.is_overdue %}text-danger{% elif bill.days_until_due <= 7 %}text-warning{% endif %}">
|
|
{{ bill.due_date|date:"M d, Y" }}
|
|
</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'billing:bill_detail' bill.bill_id %}" class="btn btn-outline-primary btn-sm" title="View Details">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
{% if bill.status == 'DRAFT' %}
|
|
<a href="{% url 'billing:bill_update' bill.bill_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>
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="">
|
|
<i class="fas fa-print me-2"></i>Print
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="">
|
|
<i class="fas fa-envelope me-2"></i>Email
|
|
</a></li>
|
|
{% if bill.status == 'DRAFT' %}
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-warning" href="">
|
|
<i class="fas fa-paper-plane me-2"></i>Submit
|
|
</a></li>
|
|
{% endif %}
|
|
{% if bill.status == 'DRAFT' or bill.status == 'PENDING' %}
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-danger" href="">
|
|
<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-file-invoice fa-3x mb-3 opacity-50"></i>
|
|
<h5>No bills found</h5>
|
|
<p>No medical bills match your current filters.</p>
|
|
<a href="{% url 'billing:bill_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-2"></i>Create First Bill
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div> <!-- /#bill-list-container -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
{% include 'partial/pagination.html' %}
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script>
|
|
// Bulk selection functionality
|
|
function toggleSelectAll() {
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
document.querySelectorAll('.bill-checkbox').forEach(cb => cb.checked = selectAllCheckbox.checked);
|
|
}
|
|
function selectAll() {
|
|
document.querySelectorAll('.bill-checkbox').forEach(cb => cb.checked = true);
|
|
document.getElementById('selectAllCheckbox').checked = true;
|
|
}
|
|
function clearSelection() {
|
|
document.querySelectorAll('.bill-checkbox').forEach(cb => cb.checked = false);
|
|
const all = document.getElementById('selectAllCheckbox');
|
|
all.checked = false; all.indeterminate = false;
|
|
}
|
|
function bulkAction(action) {
|
|
const selected = Array.from(document.querySelectorAll('.bill-checkbox:checked')).map(cb => cb.value);
|
|
if (!selected.length) { alert('Please select at least one bill.'); return; }
|
|
switch(action) {
|
|
case 'export':
|
|
window.location.href = `{% url 'billing:export_bills' %}?bills=${selected.join(',')}`;
|
|
break;
|
|
case 'print':
|
|
window.open(`{% url 'billing:print_bills' %}?bills=${selected.join(',')}`, '_blank');
|
|
break;
|
|
case 'submit':
|
|
if (confirm(`Submit ${selected.length} selected bills?`)) {
|
|
htmx.ajax('POST', '{% url "billing:bulk_submit_bills" %}', {
|
|
values: { bills: selected, csrfmiddlewaretoken: '{{ csrf_token }}' }
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
document.addEventListener('change', e => {
|
|
if (!e.target.classList.contains('bill-checkbox')) return;
|
|
const all = document.querySelectorAll('.bill-checkbox');
|
|
const checked = document.querySelectorAll('.bill-checkbox:checked').length;
|
|
const master = document.getElementById('selectAllCheckbox');
|
|
master.checked = checked === all.length;
|
|
master.indeterminate = checked > 0 && checked < all.length;
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.bg-gradient { background-image: linear-gradient(180deg, rgba(255,255,255,.15), rgba(255,255,255,0)); }
|
|
@media (max-width: 768px) {
|
|
.btn-group { display: flex; flex-direction: column; gap: .5rem; }
|
|
}
|
|
</style>
|
|
{% endblock %} |