This commit is contained in:
Marwan Alwali 2025-09-09 01:15:48 +03:00
parent 84c1fb798e
commit 43901b5bda
35 changed files with 5410 additions and 1016 deletions

View File

@ -19,6 +19,7 @@
</content>
<orderEntry type="jdk" jdkName="uv (hospital_management_system_v4)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="toastr.js" level="application" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{toastr.js}" />
</component>
</project>

View File

@ -40,6 +40,13 @@ urlpatterns = [
path('reschedule/<int:appointment_id>/', views.reschedule_appointment, name='reschedule_appointment'),
path('cancel/<int:appointment_id>/', views.cancel_appointment, name='cancel_appointment'),
path('templates/', views.AppointmentTemplateListView.as_view(), name='appointment_template_list'),
path('templates/<int:pk>/', views.AppointmentTemplateDetailView.as_view(), name='appointment_template_detail'),
path('templates/create/', views.AppointmentTemplateCreateView.as_view(), name='appointment_template_create'),
path('templates/<int:pk>/update/', views.AppointmentTemplateUpdateView.as_view(), name='appointment_template_update'),
path('templates/<int:pk>/delete/', views.AppointmentTemplateDeleteView.as_view(), name='appointment_template_delete'),
# API endpoints
# path('api/', include('appointments.api.urls')),
]

View File

@ -1002,7 +1002,7 @@ class AppointmentTemplateListView(LoginRequiredMixin, ListView):
List appointment templates.
"""
model = AppointmentTemplate
template_name = 'appointments/appointment_template_list.html'
template_name = 'appointments/templates/appointment_template_list.html'
context_object_name = 'templates'
paginate_by = 25
@ -1066,7 +1066,7 @@ class AppointmentTemplateDetailView(LoginRequiredMixin, DetailView):
Display appointment template details.
"""
model = AppointmentTemplate
template_name = 'appointments/appointment_template_detail.html'
template_name = 'appointments/templates/appointment_template_detail.html'
context_object_name = 'template'
def get_queryset(self):

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -58,16 +58,15 @@ class PatientProfileAdmin(admin.ModelAdmin):
"""
list_display = [
'mrn', 'get_full_name', 'date_of_birth', 'age', 'gender',
'phone_number', 'is_active',
'mobile_number', 'is_active',
]
list_filter = [
'tenant', 'gender', 'race', 'ethnicity', 'marital_status',
'tenant', 'gender', 'marital_status',
'is_active', 'is_deceased', 'is_vip', 'confidential_patient',
]
search_fields = [
'mrn', 'first_name', 'last_name', 'email', 'phone_number',
'ssn', 'drivers_license'
'mrn', 'id_number', 'first_name', 'last_name', 'email', 'mobile_number',
]
ordering = ['last_name', 'first_name']
@ -80,8 +79,7 @@ class PatientProfileAdmin(admin.ModelAdmin):
}),
('Demographics', {
'fields': (
'date_of_birth', 'gender', 'sex_assigned_at_birth',
'race', 'ethnicity', 'marital_status'
'date_of_birth', 'gender', 'marital_status'
)
}),
('Contact Information', {
@ -91,9 +89,6 @@ class PatientProfileAdmin(admin.ModelAdmin):
'zip_code', 'country'
)
}),
('Identification', {
'fields': ('ssn', 'drivers_license', 'drivers_license_state')
}),
('Language and Communication', {
'fields': (
'primary_language', 'interpreter_needed', 'communication_preference'

View File

@ -19,10 +19,9 @@ class PatientProfileForm(forms.ModelForm):
model = PatientProfile
fields = [
'first_name', 'last_name', 'middle_name', 'preferred_name', 'suffix',
'date_of_birth', 'gender', 'sex_assigned_at_birth', 'race', 'ethnicity',
'phone_number', 'mobile_number', 'email', 'address_line_1', 'address_line_2',
'city', 'state', 'zip_code', 'country', 'marital_status', 'occupation',
'is_active'
'id_number', 'date_of_birth', 'gender', 'phone_number', 'mobile_number',
'email', 'address_line_1', 'address_line_2', 'city', 'state', 'zip_code',
'country', 'marital_status', 'occupation', 'is_active'
]
widgets = {
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
@ -30,11 +29,9 @@ class PatientProfileForm(forms.ModelForm):
'middle_name': forms.TextInput(attrs={'class': 'form-control'}),
'preferred_name': forms.TextInput(attrs={'class': 'form-control'}),
'suffix': forms.TextInput(attrs={'class': 'form-control'}),
'id_number': forms.TextInput(attrs={'class': 'form-control'}),
'date_of_birth': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'gender': forms.Select(attrs={'class': 'form-select'}),
'sex_assigned_at_birth': forms.Select(attrs={'class': 'form-select'}),
'race': forms.Select(attrs={'class': 'form-select'}),
'ethnicity': forms.Select(attrs={'class': 'form-select'}),
'phone_number': forms.TextInput(attrs={'class': 'form-control'}),
'mobile_number': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
@ -120,7 +117,7 @@ class InsuranceInfoForm(forms.ModelForm):
fields = [
'insurance_type', 'insurance_company', 'plan_name', 'plan_type',
'policy_number', 'group_number', 'subscriber_name', 'subscriber_relationship',
'subscriber_dob', 'subscriber_ssn', 'effective_date', 'termination_date',
'subscriber_dob', 'subscriber_id_number', 'effective_date', 'termination_date',
'copay_amount', 'deductible_amount', 'out_of_pocket_max', 'is_verified',
'requires_authorization'
]
@ -134,7 +131,7 @@ class InsuranceInfoForm(forms.ModelForm):
'subscriber_name': forms.TextInput(attrs={'class': 'form-control'}),
'subscriber_relationship': forms.Select(attrs={'class': 'form-select'}),
'subscriber_dob': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'subscriber_ssn': forms.TextInput(attrs={'class': 'form-control'}),
'subscriber_id_number': forms.TextInput(attrs={'class': 'form-control'}),
'effective_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'termination_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'copay_amount': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),

View File

@ -0,0 +1,92 @@
# Generated by Django 5.2.6 on 2025-09-08 21:04
import django.core.validators
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0001_initial"),
("patients", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveIndex(
model_name="patientprofile",
name="patients_pa_ssn_07bec5_idx",
),
migrations.RemoveIndex(
model_name="patientprofile",
name="patients_pa_phone_n_223ced_idx",
),
migrations.RemoveField(
model_name="patientprofile",
name="drivers_license",
),
migrations.RemoveField(
model_name="patientprofile",
name="drivers_license_state",
),
migrations.RemoveField(
model_name="patientprofile",
name="ethnicity",
),
migrations.RemoveField(
model_name="patientprofile",
name="race",
),
migrations.RemoveField(
model_name="patientprofile",
name="sex_assigned_at_birth",
),
migrations.RemoveField(
model_name="patientprofile",
name="ssn",
),
migrations.AddField(
model_name="patientprofile",
name="id_number",
field=models.CharField(
blank=True,
help_text="Saudi National ID (10 digits)",
max_length=10,
null=True,
unique=True,
validators=[
django.core.validators.RegexValidator(
message="Saudi National ID must be exactly 10 digits",
regex="^\\d{10}$",
)
],
),
),
migrations.AlterField(
model_name="patientprofile",
name="country",
field=models.CharField(
default="Saudi Arabia", help_text="Country", max_length=100
),
),
migrations.AlterField(
model_name="patientprofile",
name="primary_language",
field=models.CharField(
default="Arabic", help_text="Primary language", max_length=50
),
),
migrations.AddIndex(
model_name="patientprofile",
index=models.Index(
fields=["id_number"], name="patients_pa_id_numb_37a581_idx"
),
),
migrations.AddIndex(
model_name="patientprofile",
index=models.Index(
fields=["mobile_number"], name="patients_pa_mobile__f5d849_idx"
),
),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 5.2.6 on 2025-09-08 21:35
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("patients", "0002_remove_patientprofile_patients_pa_ssn_07bec5_idx_and_more"),
]
operations = [
migrations.RemoveField(
model_name="insuranceinfo",
name="subscriber_ssn",
),
migrations.AddField(
model_name="insuranceinfo",
name="subscriber_id_number",
field=models.CharField(
blank=True,
help_text="Saudi National ID (10 digits)",
max_length=10,
null=True,
unique=True,
validators=[
django.core.validators.RegexValidator(
message="Saudi National ID must be exactly 10 digits",
regex="^\\d{10}$",
)
],
),
),
]

View File

@ -21,28 +21,6 @@ class PatientProfile(models.Model):
('UNKNOWN', 'Unknown'),
('PREFER_NOT_TO_SAY', 'Prefer not to say'),
]
SEX_ASSIGNED_AT_BIRTH_CHOICES = [
('MALE', 'Male'),
('FEMALE', 'Female'),
('INTERSEX', 'Intersex'),
('UNKNOWN', 'Unknown'),
]
RACE_CHOICES = [
('AMERICAN_INDIAN', 'American Indian or Alaska Native'),
('ASIAN', 'Asian'),
('BLACK', 'Black or African American'),
('PACIFIC_ISLANDER', 'Native Hawaiian or Other Pacific Islander'),
('WHITE', 'White'),
('OTHER', 'Other'),
('UNKNOWN', 'Unknown'),
('DECLINED', 'Patient Declined'),
]
ETHNICITY_CHOICES = [
('HISPANIC', 'Hispanic or Latino'),
('NON_HISPANIC', 'Not Hispanic or Latino'),
('UNKNOWN', 'Unknown'),
('DECLINED', 'Patient Declined'),
]
MARITAL_STATUS_CHOICES = [
('SINGLE', 'Single'),
('MARRIED', 'Married'),
@ -128,29 +106,6 @@ class PatientProfile(models.Model):
choices=GENDER_CHOICES,
help_text='Gender'
)
sex_assigned_at_birth = models.CharField(
max_length=20,
choices=SEX_ASSIGNED_AT_BIRTH_CHOICES,
blank=True,
null=True,
help_text='Sex assigned at birth'
)
# Race and Ethnicity
race = models.CharField(
max_length=50,
choices=RACE_CHOICES,
blank=True,
null=True,
help_text='Race'
)
ethnicity = models.CharField(
max_length=50,
choices=ETHNICITY_CHOICES,
blank=True,
null=True,
help_text='Ethnicity'
)
# Contact Information
email = models.EmailField(
@ -212,32 +167,20 @@ class PatientProfile(models.Model):
)
country = models.CharField(
max_length=100,
default='United States',
default='Saudi Arabia',
help_text='Country'
)
# Social Security and Identification
ssn = models.CharField(
max_length=11,
id_number = models.CharField(
max_length=10,
unique=True,
blank=True,
null=True,
validators=[RegexValidator(
regex=r'^\d{3}-\d{2}-\d{4}$',
message='SSN must be in format: 123-45-6789'
regex=r'^\d{10}$',
message='Saudi National ID must be exactly 10 digits'
)],
help_text='Social Security Number'
)
drivers_license = models.CharField(
max_length=50,
blank=True,
null=True,
help_text='Driver\'s license number'
)
drivers_license_state = models.CharField(
max_length=50,
blank=True,
null=True,
help_text='Driver\'s license state'
help_text='Saudi National ID (10 digits)'
)
# Marital Status and Family
@ -252,7 +195,7 @@ class PatientProfile(models.Model):
# Language and Communication
primary_language = models.CharField(
max_length=50,
default='English',
default='Arabic',
help_text='Primary language'
)
interpreter_needed = models.BooleanField(
@ -358,7 +301,6 @@ class PatientProfile(models.Model):
help_text='User who registered the patient'
)
# Photo
photo = models.ImageField(
upload_to='patient_photos/',
blank=True,
@ -366,7 +308,6 @@ class PatientProfile(models.Model):
help_text='Patient photo'
)
# Metadata
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
last_visit_date = models.DateTimeField(
@ -384,8 +325,8 @@ class PatientProfile(models.Model):
models.Index(fields=['tenant', 'mrn']),
models.Index(fields=['last_name', 'first_name']),
models.Index(fields=['date_of_birth']),
models.Index(fields=['ssn']),
models.Index(fields=['phone_number']),
models.Index(fields=['id_number']),
models.Index(fields=['mobile_number']),
models.Index(fields=['email']),
]
unique_together = ['tenant', 'mrn']
@ -711,15 +652,16 @@ class InsuranceInfo(models.Model):
null=True,
help_text='Subscriber date of birth'
)
subscriber_ssn = models.CharField(
max_length=11,
subscriber_id_number = models.CharField(
max_length=10,
unique=True,
blank=True,
null=True,
validators=[RegexValidator(
regex=r'^\d{3}-\d{2}-\d{4}$',
message='SSN must be in format: 123-45-6789'
regex=r'^\d{10}$',
message='Saudi National ID must be exactly 10 digits'
)],
help_text='Subscriber Social Security Number'
help_text='Saudi National ID (10 digits)'
)
# Coverage Information

View File

@ -38,7 +38,7 @@ urlpatterns = [
path('patient-search/', views.patient_search, name='patient_search'),
path('patient-stats/', views.patient_stats, name='patient_stats'),
path('<uuid:patient_id>/emergency-contacts/', views.emergency_contacts_list, name='emergency_contacts_list_api'),
path('insurance-info/<int:patient_id>/', views.insurance_info_list, name='insurance_info_list'),
path('insurance-info-list/<int:patient_id>/', views.insurance_info_list, name='insurance_info_list'),
path('consent-forms/<int:patient_id>/', views.consent_forms_list, name='consent_forms_list'),
path('consent-forms/detail/<int:pk>/', views.ConsentFormDetailView.as_view(), name='consent_form_detail'),
path('consent-forms/update/<int:pk>/', views.ConsentFormUpdateView.as_view(), name='consent_form_update'),

View File

@ -35,7 +35,7 @@ class PatientListView(LoginRequiredMixin, ListView):
Patient listing view.
"""
model = PatientProfile
template_name = 'patients/patient_list.html'
template_name = 'patients/profiles/patient_list.html'
context_object_name = 'patients'
paginate_by = 25
@ -610,7 +610,7 @@ class InsuranceInfoCreateView(LoginRequiredMixin, PermissionRequiredMixin, Creat
form_class = InsuranceInfoForm
template_name = "patients/insurance/insurance_form.html"
permission_required = "patients.add_insuranceinfo"
success_url = reverse_lazy("patients:insurance_info_list")
success_url = reverse_lazy("patients:insurance_list")
def dispatch(self, request, *args, **kwargs):
self.tenant = getattr(request, "tenant", None)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,167 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Appointment Template - {{ template.name }}{% endblock %}
{% block content %}
<!-- BEGIN breadcrumb -->
<ol class="breadcrumb float-xl-end">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'appointments:dashboard' %}">Appointments</a></li>
<li class="breadcrumb-item"><a href="{% url 'appointments:appointment_template_list' %}">Templates</a></li>
<li class="breadcrumb-item active">{{ template.name }}</li>
</ol>
<!-- END breadcrumb -->
<!-- BEGIN page-header -->
<h1 class="page-header">
Appointment Template: {{ template.name }}
<small>{{ template.specialty }}</small>
</h1>
<!-- END page-header -->
<div class="row">
<div class="col-xl-8">
<!-- General Info -->
<div class="panel panel-inverse mb-3">
<div class="panel-heading">
<h4 class="panel-title">General Information</h4>
</div>
<div class="panel-body">
<dl class="row mb-0">
<dt class="col-sm-4">Name</dt>
<dd class="col-sm-8">{{ template.name }}</dd>
<dt class="col-sm-4">Description</dt>
<dd class="col-sm-8">{{ template.description|default:"—" }}</dd>
<dt class="col-sm-4">Appointment Type</dt>
<dd class="col-sm-8"><span class="badge bg-secondary">{{ template.appointment_type }}</span></dd>
<dt class="col-sm-4">Specialty</dt>
<dd class="col-sm-8">{{ template.specialty }}</dd>
<dt class="col-sm-4">Duration</dt>
<dd class="col-sm-8"><span class="badge bg-primary">{{ template.duration_minutes }} minutes</span></dd>
<dt class="col-sm-4">Status</dt>
<dd class="col-sm-8">
{% if template.is_active %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-gray-500">Inactive</span>
{% endif %}
</dd>
</dl>
</div>
</div>
<!-- Scheduling Rules -->
<div class="panel panel-inverse mb-3">
<div class="panel-heading">
<h4 class="panel-title">Scheduling Rules</h4>
</div>
<div class="panel-body">
<dl class="row mb-0">
<dt class="col-sm-5">Advance Booking Days</dt>
<dd class="col-sm-7">{{ template.advance_booking_days }}</dd>
<dt class="col-sm-5">Minimum Notice (hours)</dt>
<dd class="col-sm-7">{{ template.minimum_notice_hours }}</dd>
</dl>
</div>
</div>
<!-- Requirements -->
<div class="panel panel-inverse mb-3">
<div class="panel-heading">
<h4 class="panel-title">Requirements</h4>
</div>
<div class="panel-body">
{% if template.insurance_verification_required %}
<span class="badge bg-info me-1"><i class="fa fa-id-card me-1"></i>Insurance Verification</span>
{% endif %}
{% if template.authorization_required %}
<span class="badge bg-warning text-dark"><i class="fa fa-shield-alt me-1"></i>Authorization Required</span>
{% endif %}
{% if not template.insurance_verification_required and not template.authorization_required %}
<span class="text-muted">No special requirements</span>
{% endif %}
</div>
</div>
<!-- Instructions -->
<div class="panel panel-inverse mb-3">
<div class="panel-heading">
<h4 class="panel-title">Instructions</h4>
</div>
<div class="panel-body">
<h6>Pre-Appointment Instructions</h6>
<p>{{ template.pre_appointment_instructions|linebreaks|default:"—" }}</p>
<h6 class="mt-3">Post-Appointment Instructions</h6>
<p>{{ template.post_appointment_instructions|linebreaks|default:"—" }}</p>
</div>
</div>
<!-- Required Forms -->
<div class="panel panel-inverse mb-3">
<div class="panel-heading">
<h4 class="panel-title">Required Forms</h4>
</div>
<div class="panel-body">
{% if template.required_forms %}
<ul class="mb-0">
{% for form in template.required_forms %}
<li>{{ form }}</li>
{% endfor %}
</ul>
{% else %}
<span class="text-muted">No forms required</span>
{% endif %}
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-xl-4">
<!-- Quick Actions -->
<div class="panel panel-inverse mb-3">
<div class="panel-heading">
<h4 class="panel-title">Quick Actions</h4>
</div>
<div class="panel-body">
<div class="d-grid gap-2">
<a href="{% url 'appointments:appointment_template_update' template.pk %}" class="btn btn-primary">
<i class="fa fa-edit me-1"></i>Edit
</a>
<a href="" class="btn btn-secondary">
<i class="fa fa-copy me-1"></i>Duplicate
</a>
{% if template.is_active %}
<a href="" class="btn btn-warning">
<i class="fa fa-pause-circle me-1"></i>Deactivate
</a>
{% else %}
<a href="" class="btn btn-success">
<i class="fa fa-play-circle me-1"></i>Activate
</a>
{% endif %}
</div>
</div>
</div>
<!-- Metadata -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Metadata</h4>
</div>
<div class="panel-body small">
<p><strong>Created At:</strong> {{ template.created_at|date:"M d, Y H:i" }}</p>
<p><strong>Updated At:</strong> {{ template.updated_at|date:"M d, Y H:i" }}</p>
<p><strong>Created By:</strong> {{ template.created_by.get_full_name|default:"—" }}</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,233 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Appointment Templates - Appointments{% endblock %}
{% block content %}
<!-- BEGIN breadcrumb -->
<ol class="breadcrumb float-xl-end">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'appointments:dashboard' %}">Appointments</a></li>
<li class="breadcrumb-item active">Appointment Templates</li>
</ol>
<!-- END breadcrumb -->
<!-- BEGIN page-header -->
<h1 class="page-header">
Appointment Templates
<small>Reusable configurations for common appointment types</small>
</h1>
<!-- END page-header -->
<div class="row">
<div class="col-xl-12">
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Templates</h4>
<div class="panel-heading-btn">
<a href="{% url 'appointments:appointment_template_create' %}" class="btn btn-xs btn-outline-primary me-2">
<i class="fa fa-plus me-1"></i> New Template
</a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
</div>
</div>
<div class="panel-body">
<!-- Toolbar -->
<div class="d-flex flex-wrap gap-2 align-items-center mb-3">
<div class="ms-auto d-flex flex-wrap gap-2">
<a href="#" class="btn btn-outline-success"><i class="fa fa-upload me-1"></i> Import</a>
<a href="#" class="btn btn-outline-primary"><i class="fa fa-download me-1"></i> Export</a>
</div>
</div>
<!-- Filters -->
<form method="get" class="card card-body mb-3">
<div class="row g-2">
<div class="col-md-3">
<label class="form-label">Search</label>
<input type="text" name="search" value="{{ request.GET.search|default:'' }}" class="form-control" placeholder="Name, description, department...">
</div>
<div class="col-md-3">
<label class="form-label">Appointment Type</label>
<select name="appointment_type" class="form-select">
<option value="">All types</option>
{% for t in appointment_types %}
<option value="{{ t }}" {% if request.GET.appointment_type == t %}selected{% endif %}>{{ t }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label">Department</label>
<input type="text" name="department" value="{{ request.GET.department|default:'' }}" class="form-control form-control-sm" placeholder="Department">
</div>
<div class="col-md-3">
<label class="form-label">Provider</label>
<select name="provider" class="form-select form-select-sm">
<option value="">All providers</option>
{% for p in providers %}
<option value="{{ p.id }}" {% if request.GET.provider == p.id|stringformat:'s' %}selected{% endif %}>
{{ p.get_full_name|default:p.username }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label">Status</label>
<select name="status" class="form-select form-select-sm">
<option value="">All</option>
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>Active</option>
<option value="inactive" {% if request.GET.status == 'inactive' %}selected{% endif %}>Inactive</option>
</select>
</div>
<div class="col-md-3 align-self-end">
<div class="d-flex gap-2">
<button type="submit" class="btn btn-sm btn-primary"><i class="fa fa-filter me-1"></i> Filter</button>
<a href="{% url 'appointments:appointment_template_list' %}" class="btn btn-sm btn-success">Reset</a>
</div>
</div>
</div>
</form>
<!-- Results -->
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Name</th>
<th>Appointment Type</th>
<th>Specialty</th>
<th class="text-center">Duration</th>
<th>Booking Rules</th>
<th>Requirements</th>
<th>Status</th>
<th class="text-nowrap">Created / Updated</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
{% for t in templates %}
<tr>
<td>
<div class="fw-semibold">
<a href="{% url 'appointments:appointment_template_detail' t.pk %}">{{ t.name }}</a>
</div>
{% if t.description %}
<div class="text-muted small text-truncate" style="max-width: 360px;">{{ t.description }}</div>
{% endif %}
</td>
<td>
<span class="badge bg-secondary">{{ t.appointment_type }}</span>
</td>
<td>{{ t.specialty }}</td>
<td class="text-center">
<span class="badge bg-primary">{{ t.duration_minutes }}m</span>
</td>
<td class="small">
<div><i class="fa fa-calendar me-1 text-muted"></i>Advance: {{ t.advance_booking_days }} days</div>
<div><i class="fa fa-bell me-1 text-muted"></i>Notice: {{ t.minimum_notice_hours }} hrs</div>
</td>
<td>
{% if t.insurance_verification_required %}
<span class="badge bg-info me-1"><i class="fa fa-id-card me-1"></i>Insurance</span>
{% endif %}
{% if t.authorization_required %}
<span class="badge bg-warning text-dark"><i class="fa fa-shield-alt me-1"></i>Auth</span>
{% endif %}
{% if not t.insurance_verification_required and not t.authorization_required %}
<span class="text-muted small">None</span>
{% endif %}
</td>
<td>
{% if t.is_active %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-gray-500">Inactive</span>
{% endif %}
</td>
<td class="text-nowrap small">
<div><i class="fa fa-user me-1 text-muted"></i>{{ t.created_by.get_full_name|default:"—" }}</div>
<div class="text-muted">{{ t.updated_at|date:"M d, Y H:i" }}</div>
</td>
<td class="text-end">
<div class="btn-group">
<a href="{% url 'appointments:appointment_template_detail' t.pk %}" class="btn btn-outline-primary btn-sm" title="View">
<i class="fa fa-eye"></i>
</a>
<a href="{% url 'appointments:appointment_template_update' t.pk %}" class="btn btn-outline-secondary btn-sm" title="Edit">
<i class="fa fa-edit"></i>
</a>
<button class="btn btn-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" title="More">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="">
<i class="fa fa-copy me-2"></i>Duplicate
</a>
</li>
<li><hr class="dropdown-divider"></li>
{% if t.is_active %}
<li>
<a class="dropdown-item text-warning" href="">
<i class="fa fa-pause-circle me-2"></i>Deactivate
</a>
</li>
{% else %}
<li>
<a class="dropdown-item text-success" href="">
<i class="fa fa-play-circle me-2"></i>Activate
</a>
</li>
{% endif %}
</ul>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center py-5">
<div class="text-muted">
<i class="fa fa-clipboard-list fa-2x mb-2"></i>
<div>No templates found.</div>
<div class="mt-2">
<a href="{% url 'appointments:appointment_template_create' %}" class="btn btn-primary btn-sm">
<i class="fa fa-plus me-1"></i>Create Template
</a>
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,5 +1,3 @@
{% load i18n static%}
<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}
@ -19,22 +17,9 @@
<link href="{% static 'css/vendor.min.css' %}" rel="stylesheet" />
<link href="{% static 'css/default/app.min.css' %}" rel="stylesheet" />
{# <link href="{% static 'css/apple/app.min.css' %}" rel="stylesheet" />#}
{# <link href="{% static 'css/transparent/app.min.css' %}" rel="stylesheet" />#}
<link href="{% static 'css/custom.css' %}" rel="stylesheet" />
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" rel="stylesheet"/>
</head>
<body>
<!-- Your page content -->
{# <link href="{% static 'css/custom.css' %}" rel="stylesheet" />#}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.6/dist/htmx.min.js"></script>
</body>
{# <script src="{% static 'plugins/apexcharts/dist/apexcharts.min.js' %}"></script>#}
<!-- HTMX -->
<script src="{% static 'js/htmx.min.js' %}"></script>
<!-- ================== END core-css ================== -->
{% block css %}
@ -168,7 +153,7 @@
</main>
<div hx-boost="false">
<div>
{% block content %}
{% endblock %}
@ -184,72 +169,18 @@
{% endblock %}
<!-- ================== BEGIN core-js ================== -->
<script src="{% static 'js/htmx.min.js' %}"></script>
<script src="{% static 'js/vendor.min.js' %}"></script>
<script src="{% static 'js/app.min.js' %}"></script>
{#<script src="{% static 'js/custom.js' %}"></script>#}
<!-- ================== END core-js ================== -->
{% block js %}
<script>
// HTMX configuration
htmx.config.globalViewTransitions = true;
// Auto-refresh indicators
document.addEventListener('htmx:beforeRequest', function(evt) {
if (evt.detail.target.classList.contains('auto-refresh')) {
evt.detail.target.style.opacity = '0.7';
}
});
document.addEventListener('htmx:afterRequest', function(evt) {
if (evt.detail.target.classList.contains('auto-refresh')) {
evt.detail.target.style.opacity = '1';
}
});
{#// Define a default palette if no colors provided#}
{#const defaultPalette = ['#FF6384','#36A2EB','#FFCE56','#4BC0C0','#9966FF','#FF9F40'];#}
{##}
{#// Function to (re)create all charts#}
{#function recreateAllChartsByClass() {#}
{# document.querySelectorAll('.apex-chart').forEach(el => {#}
{# const optionsText = el.getAttribute('data-options');#}
{# if (!optionsText) return;#}
{##}
{# let options;#}
{# try {#}
{# options = JSON.parse(optionsText);#}
{# } catch (e) {#}
{# console.error('❌ Invalid JSON in data-options for', el, e);#}
{# return;#}
{# }#}
{##}
{# if (!options.colors) {#}
{# options.colors = defaultPalette;#}
{# }#}
{##}
{# if (el._apexInstance && typeof el._apexInstance.destroy === 'function') {#}
{# el._apexInstance.destroy();#}
{# }#}
{##}
{# const chart = new ApexCharts(el, options);#}
{# chart.render();#}
{# el._apexInstance = chart;#}
{# });#}
{# }#}
{##}
{#window.addEventListener('load', function() {#}
{# setTimeout(recreateAllChartsByClass, 10);#}
{# });#}
/*$(document).on('sortstop', function() {
recreateAllChartsByClass();
});
*/
</script>
{% endblock %}
</body>
</html>

View File

@ -3,9 +3,9 @@
{% block title %}Patients Dashboard{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
{% block css %}
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<style>
.dashboard-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@ -830,10 +830,10 @@
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/chart.js/chart.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
{% block js %}
<script src="{% static 'plugins/chart.js/dist/chart.js' %}"></script>
<script src="{% static 'plugins/datatables.net/js/dataTables.min.js' %}"></script>
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script>
$(document).ready(function() {

View File

@ -4,7 +4,7 @@
{% block title %}{% if object %}Edit{% else %}Add{% endif %} Insurance - Patients{% endblock %}
{% block css %}
<link href="{% static 'assets/plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
@ -12,12 +12,18 @@
<ol class="breadcrumb float-xl-end">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'patients:patient_list' %}">Patients</a></li>
{% if object %}
<li class="breadcrumb-item"><a href="{% url 'patients:patient_detail' object.patient.pk %}">{{ object.patient.get_full_name }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'patients:insurance_detail' object.pk %}">{{ object.insurance_provider }}</a></li>
<li class="breadcrumb-item active">Edit</li>
<li class="breadcrumb-item">
<a href="{% url 'patients:patient_detail' object.patient.pk %}">{{ object.patient.get_full_name }}</a>
</li>
<li class="breadcrumb-item active">Edit Insurance</li>
{% else %}
<li class="breadcrumb-item"><a href="{% url 'patients:insurance_list' %}">Insurance</a></li>
{% if patient %}
<li class="breadcrumb-item">
<a href="{% url 'patients:patient_detail' patient.pk %}">{{ patient.get_full_name }}</a>
</li>
{% endif %}
<li class="breadcrumb-item active">Add Insurance</li>
{% endif %}
</ol>
@ -26,7 +32,9 @@
<!-- BEGIN page-header -->
<h1 class="page-header">
{% if object %}Edit Insurance{% else %}Add Insurance{% endif %}
<small>{% if object %}{{ object.insurance_provider }}{% else %}New Insurance Policy{% endif %}</small>
<small>
{% if object %}{{ object.insurance_company }}{% elif patient %}for {{ patient.get_full_name }}{% else %}New Insurance Policy{% endif %}
</small>
</h1>
<!-- END page-header -->
@ -37,178 +45,203 @@
<div class="panel-heading">
<h4 class="panel-title">Insurance Information</h4>
<div class="panel-heading-btn">
<button type="button" class="btn btn-xs btn-info me-2" onclick="saveDraft()">
<i class="fa fa-save"></i> Save Draft
</button>
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
</div>
</div>
<div class="panel-body">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<form method="post" id="insurance-form" novalidate>
{% csrf_token %}
<!-- POLICY BASICS -->
<div class="row mb-3">
<div class="col-md-6">
<div class="form-floating">
{{ form.patient }}
<label for="{{ form.patient.id_for_label }}">Patient <span class="text-danger">*</span></label>
{% if form.patient.errors %}
<div class="invalid-feedback d-block">{{ form.patient.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-floating">
<div class="mb-3">
<label for="{{ form.insurance_type.id_for_label }}" class="form-label">Insurance Type <span class="text-danger">*</span></label>
{{ form.insurance_type }}
<label for="{{ form.insurance_type.id_for_label }}">Insurance Type <span class="text-danger">*</span></label>
{% if form.insurance_type.errors %}
<div class="invalid-feedback d-block">{{ form.insurance_type.errors.0 }}</div>
{% endif %}
{% if form.insurance_type.errors %}<div class="invalid-feedback d-block">{{ form.insurance_type.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.insurance_company.id_for_label }}" class="form-label">Insurance Company <span class="text-danger">*</span></label>
{{ form.insurance_company }}
{% if form.insurance_company.errors %}<div class="invalid-feedback d-block">{{ form.insurance_company.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-floating">
{{ form.insurance_provider }}
<label for="{{ form.insurance_provider.id_for_label }}">Insurance Provider <span class="text-danger">*</span></label>
{% if form.insurance_provider.errors %}
<div class="invalid-feedback d-block">{{ form.insurance_provider.errors.0 }}</div>
{% endif %}
<div class="mb-3">
<label for="{{ form.plan_name.id_for_label }}" class="form-label">Plan Name</label>
{{ form.plan_name }}
{% if form.plan_name.errors %}<div class="invalid-feedback d-block">{{ form.plan_name.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-floating">
<div class="mb-3">
<label for="{{ form.plan_type.id_for_label }}" class="form-label">Plan Type</label>
{{ form.plan_type }}
{% if form.plan_type.errors %}<div class="invalid-feedback d-block">{{ form.plan_type.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.policy_number.id_for_label }}" class="form-label">Policy Number <span class="text-danger">*</span></label>
{{ form.policy_number }}
<label for="{{ form.policy_number.id_for_label }}">Policy Number <span class="text-danger">*</span></label>
{% if form.policy_number.errors %}
<div class="invalid-feedback d-block">{{ form.policy_number.errors.0 }}</div>
{% endif %}
</div>
{% if form.policy_number.errors %}<div class="invalid-feedback d-block">{{ form.policy_number.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-floating">
{{ form.member_id }}
<label for="{{ form.member_id.id_for_label }}">Member ID</label>
{% if form.member_id.errors %}
<div class="invalid-feedback d-block">{{ form.member_id.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-floating">
<div class="mb-3">
<label for="{{ form.group_number.id_for_label }}" class="form-label">Group Number</label>
{{ form.group_number }}
<label for="{{ form.group_number.id_for_label }}">Group Number</label>
{% if form.group_number.errors %}
<div class="invalid-feedback d-block">{{ form.group_number.errors.0 }}</div>
{% endif %}
{% if form.group_number.errors %}<div class="invalid-feedback d-block">{{ form.group_number.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<!-- SUBSCRIBER -->
<div class="row mb-3">
<div class="col-12">
<h6 class="border-bottom pb-2">Subscriber</h6>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.subscriber_name.id_for_label }}" class="form-label">Subscriber Name</label>
{{ form.subscriber_name }}
{% if form.subscriber_name.errors %}<div class="invalid-feedback d-block">{{ form.subscriber_name.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.subscriber_relationship.id_for_label }}" class="form-label">Relationship</label>
{{ form.subscriber_relationship }}
{% if form.subscriber_relationship.errors %}<div class="invalid-feedback d-block">{{ form.subscriber_relationship.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.subscriber_dob.id_for_label }}" class="form-label">Subscriber DOB</label>
{{ form.subscriber_dob }}
{% if form.subscriber_dob.errors %}<div class="invalid-feedback d-block">{{ form.subscriber_dob.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.subscriber_id_number.id_for_label }}" class="form-label">Subscriber ID Number</label>
{{ form.subscriber_id_number }}
{% if form.subscriber_id_number.errors %}<div class="invalid-feedback d-block">{{ form.subscriber_id_number.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<!-- DATES -->
<div class="row mb-3">
<div class="col-md-6">
<div class="form-floating">
<div class="mb-3">
<label for="{{ form.effective_date.id_for_label }}" class="form-label">Effective Date</label>
{{ form.effective_date }}
<label for="{{ form.effective_date.id_for_label }}">Effective Date</label>
{% if form.effective_date.errors %}
<div class="invalid-feedback d-block">{{ form.effective_date.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-floating">
{{ form.expiration_date }}
<label for="{{ form.expiration_date.id_for_label }}">Expiration Date</label>
{% if form.expiration_date.errors %}
<div class="invalid-feedback d-block">{{ form.expiration_date.errors.0 }}</div>
{% endif %}
</div>
{% if form.effective_date.errors %}<div class="invalid-feedback d-block">{{ form.effective_date.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-floating">
{{ form.copay }}
<label for="{{ form.copay.id_for_label }}">Copay Amount ($)</label>
{% if form.copay.errors %}
<div class="invalid-feedback d-block">{{ form.copay.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-floating">
{{ form.deductible }}
<label for="{{ form.deductible.id_for_label }}">Deductible Amount ($)</label>
{% if form.deductible.errors %}
<div class="invalid-feedback d-block">{{ form.deductible.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-floating">
{{ form.status }}
<label for="{{ form.status.id_for_label }}">Status <span class="text-danger">*</span></label>
{% if form.status.errors %}
<div class="invalid-feedback d-block">{{ form.status.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-check mt-3">
{{ form.is_primary }}
<label class="form-check-label" for="{{ form.is_primary.id_for_label }}">
Primary Insurance
</label>
{% if form.is_primary.errors %}
<div class="invalid-feedback d-block">{{ form.is_primary.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<div class="mb-3">
<label for="{{ form.coverage_details.id_for_label }}" class="form-label">Coverage Details</label>
{{ form.coverage_details }}
{% if form.coverage_details.errors %}
<div class="invalid-feedback d-block">{{ form.coverage_details.errors.0 }}</div>
{% endif %}
<div class="form-text">Describe what services are covered, limitations, and special requirements.</div>
<label for="{{ form.termination_date.id_for_label }}" class="form-label">Termination Date</label>
{{ form.termination_date }}
{% if form.termination_date.errors %}<div class="invalid-feedback d-block">{{ form.termination_date.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<!-- FINANCIALS -->
<div class="row mb-3">
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.notes.id_for_label }}" class="form-label">Additional Notes</label>
{{ form.notes }}
{% if form.notes.errors %}
<div class="invalid-feedback d-block">{{ form.notes.errors.0 }}</div>
{% endif %}
<div class="form-text">Any additional information about this insurance policy.</div>
<label for="{{ form.copay_amount.id_for_label }}" class="form-label">Copay Amount ($)</label>
{{ form.copay_amount }}
{% if form.copay_amount.errors %}<div class="invalid-feedback d-block">{{ form.copay_amount.errors.0 }}</div>{% endif %}
</div>
</div>
<!-- Form Actions -->
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.deductible_amount.id_for_label }}" class="form-label">Deductible ($)</label>
{{ form.deductible_amount }}
{% if form.deductible_amount.errors %}<div class="invalid-feedback d-block">{{ form.deductible_amount.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.out_of_pocket_max.id_for_label }}" class="form-label">Out-of-Pocket Max ($)</label>
{{ form.out_of_pocket_max }}
{% if form.out_of_pocket_max.errors %}<div class="invalid-feedback d-block">{{ form.out_of_pocket_max.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<!-- FLAGS -->
<div class="row mb-4">
<div class="col-md-6">
<div class="form-check">
{{ form.is_verified }}
<label class="form-check-label ms-1" for="{{ form.is_verified.id_for_label }}">Verified</label>
{% if form.is_verified.errors %}<div class="invalid-feedback d-block">{{ form.is_verified.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-check">
{{ form.requires_authorization }}
<label class="form-check-label ms-1" for="{{ form.requires_authorization.id_for_label }}">Requires Authorization</label>
{% if form.requires_authorization.errors %}<div class="invalid-feedback d-block">{{ form.requires_authorization.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<!-- ACTIONS -->
<div class="d-flex justify-content-between">
<div>
<a href="{% if object %}{% url 'patients:insurance_detail' object.pk %}{% else %}{% url 'patients:insurance_list' %}{% endif %}" class="btn btn-secondary">
<a href="{% url 'patients:insurance_info_list' patient.pk %}" class="btn btn-secondary">
<i class="fa fa-arrow-left me-2"></i>Cancel
</a>
</div>
<div>
<button type="button" class="btn btn-info me-2" onclick="saveDraft()">
<i class="fa fa-save me-2"></i>Save Draft
</button>
<button type="submit" class="btn btn-primary">
<i class="fa fa-check me-2"></i>{% if object %}Update{% else %}Create{% endif %} Insurance
</button>
</div>
</div>
</form>
</div>
</div>
@ -216,138 +249,87 @@
</div>
<div class="col-xl-4">
<!-- BEGIN panel -->
<!-- PATIENT CARD -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Patient</h4>
</div>
<div class="panel-body">
{% with p=object.patient|default:patient %}
{% if p %}
<div class="text-center mb-3">
<div class="w-60px h-60px bg-primary rounded-circle d-flex align-items-center justify-content-center mx-auto mb-2">
<i class="fa fa-user text-white"></i>
</div>
<h6 class="mb-1">{{ p.get_full_name }}</h6>
<small class="text-muted">{{ p.patient_id }}</small>
</div>
<table class="table table-borderless table-sm">
<tr><td class="fw-bold" width="60">DOB:</td><td>{{ p.date_of_birth|date:"M d, Y" }}</td></tr>
<tr><td class="fw-bold">Age:</td><td>{{ p.age }} years</td></tr>
<tr><td class="fw-bold">Gender:</td><td>{{ p.get_gender_display }}</td></tr>
</table>
<div class="d-grid">
<a href="{% url 'patients:patient_detail' p.pk %}" class="btn btn-outline-primary btn-sm">
<i class="fa fa-user me-2"></i>View Patient
</a>
</div>
{% else %}
<div class="alert alert-warning mb-0">No patient context found.</div>
{% endif %}
{% endwith %}
</div>
</div>
<!-- HELP -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Help & Guidelines</h4>
</div>
<div class="panel-body">
<div class="alert alert-info">
<h6 class="alert-heading">Insurance Information Tips</h6>
<h6 class="alert-heading">Tips</h6>
<ul class="mb-0 small">
<li>Verify all information with the insurance card</li>
<li>Primary insurance should be billed first</li>
<li>Check effective and expiration dates carefully</li>
<li>Include group number if available</li>
<li>Document any special coverage requirements</li>
<li>Verify against the insurance card.</li>
<li>Check effective and termination dates.</li>
<li>Record subscriber info if different from patient.</li>
<li>Mark “Requires Authorization” when pre-auth is needed.</li>
</ul>
</div>
<div class="card border-primary mb-3">
<div class="card-header bg-primary text-white">
<h6 class="card-title mb-0">Required Fields</h6>
<h6 class="card-title mb-0">Frequently Required</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0 small">
<li><i class="fa fa-check text-success me-2"></i>Patient</li>
<li><i class="fa fa-check text-success me-2"></i>Insurance Provider</li>
<li><i class="fa fa-check text-success me-2"></i>Policy Number</li>
<div class="card-body small">
<ul class="list-unstyled mb-0">
<li><i class="fa fa-check text-success me-2"></i>Insurance Type</li>
<li><i class="fa fa-check text-success me-2"></i>Status</li>
<li><i class="fa fa-check text-success me-2"></i>Insurance Company</li>
<li><i class="fa fa-check text-success me-2"></i>Policy Number</li>
</ul>
</div>
</div>
<div class="card border-warning mb-3">
<div class="card border-warning">
<div class="card-header bg-warning text-dark">
<h6 class="card-title mb-0">Validation Rules</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0 small">
<li><i class="fa fa-exclamation-triangle text-warning me-2"></i>Only one primary insurance per patient</li>
<li><i class="fa fa-exclamation-triangle text-warning me-2"></i>Effective date cannot be in the future</li>
<li><i class="fa fa-exclamation-triangle text-warning me-2"></i>Expiration date must be after effective date</li>
<li><i class="fa fa-exclamation-triangle text-warning me-2"></i>Policy number must be unique per provider</li>
<div class="card-body small">
<ul class="list-unstyled mb-0">
<li><i class="fa fa-exclamation-triangle text-warning me-2"></i>Termination date must be after effective date.</li>
<li><i class="fa fa-exclamation-triangle text-warning me-2"></i>Policy number often must be unique by company.</li>
</ul>
</div>
</div>
<div class="card border-success">
<div class="card-header bg-success text-white">
<h6 class="card-title mb-0">Quick Actions</h6>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="verifyWithProvider()">
<i class="fa fa-check-circle me-2"></i>Verify with Provider
</button>
<button type="button" class="btn btn-outline-info btn-sm" onclick="checkEligibility()">
<i class="fa fa-search me-2"></i>Check Eligibility
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="scanInsuranceCard()">
<i class="fa fa-camera me-2"></i>Scan Insurance Card
</button>
</div>
</div>
</div>
</div>
</div>
<!-- END panel -->
{% if object %}
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Current Patient</h4>
</div>
<div class="panel-body">
<div class="text-center mb-3">
<div class="w-60px h-60px bg-primary rounded-circle d-flex align-items-center justify-content-center mx-auto mb-2">
<i class="fa fa-user text-white"></i>
</div>
<h6 class="mb-1">{{ object.patient.get_full_name }}</h6>
<small class="text-muted">{{ object.patient.patient_id }}</small>
</div>
<table class="table table-borderless table-sm">
<tr>
<td class="fw-bold" width="60">DOB:</td>
<td>{{ object.patient.date_of_birth|date:"M d, Y" }}</td>
</tr>
<tr>
<td class="fw-bold">Age:</td>
<td>{{ object.patient.age }} years</td>
</tr>
<tr>
<td class="fw-bold">Gender:</td>
<td>{{ object.patient.get_gender_display }}</td>
</tr>
</table>
<div class="d-grid">
<a href="{% url 'patients:patient_detail' object.patient.pk %}" class="btn btn-outline-primary btn-sm">
<i class="fa fa-user me-2"></i>View Patient
</a>
</div>
</div>
</div>
<!-- END panel -->
{% endif %}
<!-- BEGIN panel -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Form Status</h4>
</div>
<div class="panel-body">
<div id="form-status">
<div class="alert alert-secondary">
<i class="fa fa-info-circle me-2"></i>
<span id="status-text">Form not saved</span>
</div>
</div>
<div class="small text-muted">
<div>Last saved: <span id="last-saved">Never</span></div>
<div>Auto-save: <span class="text-success">Enabled</span></div>
</div>
</div>
</div>
<!-- END panel -->
</div>
</div>
<!-- Verification Modal -->
<!-- Verification Modal (hooks kept for later integrations) -->
<div class="modal fade" id="verificationModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
@ -356,9 +338,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="verification-results">
<!-- Verification results will be loaded here -->
</div>
<div id="verification-results"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
@ -369,231 +349,63 @@
{% endblock %}
{% block js %}
<script src="{% static 'assets/plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize date pickers
$('#{{ form.effective_date.id_for_label }}, #{{ form.expiration_date.id_for_label }}').datepicker({
$(function () {
// Datepickers (optional; widgets already type="date")
$('#{{ form.effective_date.id_for_label }}, #{{ form.termination_date.id_for_label }}').datepicker({
format: 'yyyy-mm-dd',
autoclose: true,
todayHighlight: true
});
// Form validation
$('#insurance-form').on('submit', function(e) {
if (!validateForm()) {
e.preventDefault();
}
});
// Auto-save functionality
var autoSaveTimer;
$('#insurance-form input, #insurance-form select, #insurance-form textarea').on('change input', function() {
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(function() {
saveDraft();
}, 5000); // Auto-save after 5 seconds of inactivity
});
// Insurance type change handler
$('#{{ form.insurance_type.id_for_label }}').on('change', function() {
var type = $(this).val();
if (type === 'PRIMARY') {
$('#{{ form.is_primary.id_for_label }}').prop('checked', true);
checkPrimaryInsurance();
}
});
// Primary insurance checkbox handler
$('#{{ form.is_primary.id_for_label }}').on('change', function() {
if ($(this).is(':checked')) {
checkPrimaryInsurance();
}
});
// Policy number validation
$('#{{ form.policy_number.id_for_label }}').on('blur', function() {
validatePolicyNumber();
});
// Date validation
$('#{{ form.effective_date.id_for_label }}, #{{ form.expiration_date.id_for_label }}').on('change', function() {
validateDates();
});
});
function validateForm() {
var isValid = true;
var errors = [];
// Required field validation
var requiredFields = ['{{ form.patient.id_for_label }}', '{{ form.insurance_provider.id_for_label }}', '{{ form.policy_number.id_for_label }}', '{{ form.insurance_type.id_for_label }}', '{{ form.status.id_for_label }}'];
requiredFields.forEach(function(fieldId) {
var field = $('#' + fieldId);
if (!field.val()) {
field.addClass('is-invalid');
errors.push(field.closest('.form-floating').find('label').text().replace(' *', '') + ' is required');
isValid = false;
} else {
field.removeClass('is-invalid');
}
});
// Date validation
if (!validateDates()) {
isValid = false;
}
if (!isValid) {
toastr.error('Please fix the following errors:\n' + errors.join('\n'));
}
return isValid;
}
// Client-side sanity check for dates
function validateDates() {
var effectiveDate = $('#{{ form.effective_date.id_for_label }}').val();
var expirationDate = $('#{{ form.expiration_date.id_for_label }}').val();
if (effectiveDate && expirationDate) {
var effective = new Date(effectiveDate);
var expiration = new Date(expirationDate);
if (expiration <= effective) {
$('#{{ form.expiration_date.id_for_label }}').addClass('is-invalid');
toastr.error('Expiration date must be after effective date');
const eff = $('#{{ form.effective_date.id_for_label }}').val();
const term = $('#{{ form.termination_date.id_for_label }}').val();
if (eff && term) {
const e = new Date(eff);
const t = new Date(term);
if (t <= e) {
$('#{{ form.termination_date.id_for_label }}').addClass('is-invalid');
return false;
} else {
$('#{{ form.expiration_date.id_for_label }}').removeClass('is-invalid');
$('#{{ form.termination_date.id_for_label }}').removeClass('is-invalid');
}
}
return true;
}
function checkPrimaryInsurance() {
var patientId = $('#{{ form.patient.id_for_label }}').val();
if (!patientId) return;
$('#{{ form.effective_date.id_for_label }}, #{{ form.termination_date.id_for_label }}').on('change', validateDates);
$.ajax({
url: '{% url "patients:check_primary_insurance" %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'patient_id': patientId,
'current_insurance_id': {% if object %}'{{ object.pk }}'{% else %}null{% endif %}
},
success: function(response) {
if (response.has_primary) {
toastr.warning('Patient already has a primary insurance. This will replace the existing primary insurance.');
}
}
});
}
function validatePolicyNumber() {
var policyNumber = $('#{{ form.policy_number.id_for_label }}').val();
var provider = $('#{{ form.insurance_provider.id_for_label }}').val();
if (policyNumber && provider) {
$.ajax({
url: '{% url "patients:validate_policy_number" %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'policy_number': policyNumber,
'provider': provider,
'current_insurance_id': {% if object %}'{{ object.pk }}'{% else %}null{% endif %}
},
success: function(response) {
if (!response.is_valid) {
$('#{{ form.policy_number.id_for_label }}').addClass('is-invalid');
toastr.error('Policy number already exists for this provider');
// Minimal required validation on submit (server remains source of truth)
$('#insurance-form').on('submit', function (e) {
let ok = true;
const requiredIds = [
'{{ form.insurance_type.id_for_label }}',
'{{ form.insurance_company.id_for_label }}',
'{{ form.policy_number.id_for_label }}'
];
requiredIds.forEach(function(id){
const $f = $('#'+id);
if(!$f.val()){
$f.addClass('is-invalid');
ok = false;
} else {
$('#{{ form.policy_number.id_for_label }}').removeClass('is-invalid');
}
$f.removeClass('is-invalid');
}
});
}
}
function saveDraft() {
var formData = $('#insurance-form').serialize();
$.ajax({
url: '{% url "patients:save_insurance_draft" %}',
method: 'POST',
data: formData,
success: function(response) {
updateFormStatus('Draft saved', 'success');
$('#last-saved').text(new Date().toLocaleTimeString());
},
error: function() {
updateFormStatus('Failed to save draft', 'danger');
if (!validateDates()) ok = false;
if (!ok) {
e.preventDefault();
alert('Please fill required fields and fix highlighted errors.');
}
});
}
function updateFormStatus(message, type) {
var alertClass = 'alert-' + type;
$('#form-status').html('<div class="alert ' + alertClass + '"><i class="fa fa-info-circle me-2"></i>' + message + '</div>');
}
function verifyWithProvider() {
var provider = $('#{{ form.insurance_provider.id_for_label }}').val();
var policyNumber = $('#{{ form.policy_number.id_for_label }}').val();
if (!provider || !policyNumber) {
toastr.error('Please enter insurance provider and policy number first');
return;
}
$.ajax({
url: '{% url "patients:verify_with_provider" %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'provider': provider,
'policy_number': policyNumber
},
beforeSend: function() {
$('#verification-results').html('<div class="text-center"><i class="fa fa-spinner fa-spin fa-2x"></i><br>Verifying with provider...</div>');
$('#verificationModal').modal('show');
},
success: function(response) {
$('#verification-results').html(response.html);
},
error: function() {
$('#verification-results').html('<div class="alert alert-danger">Failed to verify with provider. Please try again.</div>');
}
// Optional: simple uniqueness hint (adjust endpoint if you add it)
$('#{{ form.policy_number.id_for_label }}, #{{ form.insurance_company.id_for_label }}').on('blur', function(){
// Placeholder for future AJAX uniqueness check
});
});
}
{#function checkEligibility() {#}
{# var formData = $('#insurance-form').serialize();#}
{# #}
{# $.ajax({#}
{# url: '{% url "patients:check_eligibility" pk %}',#}
{# method: 'POST',#}
{# data: formData,#}
{# beforeSend: function() {#}
{# $('#verification-results').html('<div class="text-center"><i class="fa fa-spinner fa-spin fa-2x"></i><br>Checking eligibility...</div>');#}
{# $('#verificationModal').modal('show');#}
{# },#}
{# success: function(response) {#}
{# $('#verification-results').html(response.html);#}
{# },#}
{# error: function() {#}
{# $('#verification-results').html('<div class="alert alert-danger">Failed to check eligibility. Please try again.</div>');#}
{# }#}
{# });#}
{# }#}
function scanInsuranceCard() {
toastr.info('Insurance card scanning feature coming soon!');
}
</script>
{% endblock %}

View File

@ -15,8 +15,7 @@
<span class="badge bg-success">Verified</span>
{% else %}
<button class="btn btn-sm btn-outline-primary"
hx-post="{% url 'patients:verify_insurance' insurance.id %}"
hx-swap="outerHTML">
hx-post="{% url 'patients:verify_insurance' insurance.id %}">
Verify
</button>
{% endif %}

View File

@ -45,9 +45,6 @@
</div>
<div>{{ patient.get_gender_display }}</div>
<div>DOB: {{ patient.date_of_birth|date:"M d, Y" }}</div>
{% if patient.race %}
<small class="text-muted">{{ patient.get_race_display }}</small>
{% endif %}
</td>
<td>
{% if patient.email %}
@ -73,9 +70,9 @@
<div>{{ primary_insurance.insurance_company }}</div>
<small class="text-muted">{{ primary_insurance.plan_name }}</small>
{% if primary_insurance.is_verified %}
<br><span class="badge bg-success">Verified</span>
<br><span class="badge bg-success fs-9px">Verified</span>
{% else %}
<br><span class="badge bg-warning">Unverified</span>
<br><span class="badge bg-warning fs-9px">Unverified</span>
{% endif %}
{% else %}
<span class="text-muted">No insurance</span>
@ -85,23 +82,23 @@
<td>
<div>
{% if patient.is_deceased %}
<span class="badge bg-dark">Deceased</span>
<span class="badge bg-dark fs-9px">Deceased</span>
{% elif patient.is_active %}
<span class="badge bg-success">Active</span>
<span class="badge bg-success fs-9px">Active</span>
{% else %}
<span class="badge bg-secondary">Inactive</span>
<span class="badge bg-secondary fs-9px">Inactive</span>
{% endif %}
</div>
{% if patient.allergies %}
<div class="mt-1">
<span class="badge bg-danger" title="Has allergies">
<span class="badge bg-danger fs-9px" title="Has allergies">
<i class="fas fa-exclamation-triangle"></i> Allergies
</span>
</div>
{% endif %}
{% if patient.medical_alerts %}
<div class="mt-1">
<span class="badge bg-warning text-dark" title="Medical alerts">
<span class="badge bg-warning text-dark fs-9px" title="Medical alerts">
<i class="fas fa-bell"></i> Alerts
</span>
</div>
@ -129,8 +126,10 @@
<button type="button"
class="btn btn-outline-info"
title="View Insurance"
hx-get="{% url 'patients:insurance_info_list' patient.id %}"
hx-get="{% url 'patients:insurance_info_list' patient.pk %}"
hx-target="#insurance-modal-body"
hx-swap="innerHTML"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#insuranceModal">
<i class="fas fa-shield-alt"></i>
@ -138,12 +137,18 @@
<button type="button"
class="btn btn-outline-success"
title="View Consents"
hx-get="{% url 'patients:consent_forms_list' patient.id %}"
hx-get="{% url 'patients:consent_forms_list' patient.pk %}"
hx-target="#consent-modal-body"
hx-swap="innerHTML"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#consentModal">
<i class="fas fa-file-signature"></i>
</button>
<button type="button"
class="btn btn-outline-warning"
data-bs-toggle="modal"
data-bs-target="#quickActionsModal"></button>
</div>
</td>
</tr>
@ -157,10 +162,11 @@
{% endfor %}
</tbody>
</table>
</div>
<!-- prevents close & backdrop churn -->
<!-- Insurance Modal -->
<div class="modal fade" id="insuranceModal" tabindex="-1" aria-labelledby="insuranceModalLabel" aria-hidden="true">
<div class="modal " id="insuranceModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
@ -177,7 +183,7 @@
</div>
<!-- Consent Modal -->
<div class="modal fade" id="consentModal" tabindex="-1" aria-labelledby="consentModalLabel" aria-hidden="true">
<div class="modal " id="consentModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
@ -193,3 +199,54 @@
</div>
</div>
</div>
<div class="modal fade" id="quickActionsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-bolt me-2"></i>Quick Actions
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6 mb-3">
<a href="{% url 'patients:patient_registration' %}" class="btn btn-outline-primary w-100 text-start">
<i class="fas fa-user-plus me-3"></i>Register New Patient
</a>
</div>
<div class="col-md-6 mb-3">
<a href="{% url 'appointments:appointment_create' %}" class="btn btn-outline-success w-100 text-start">
<i class="fas fa-calendar-plus me-3"></i>Schedule Appointment
</a>
</div>
<div class="col-md-6 mb-3">
<a href="" class="btn btn-outline-info w-100 text-start">
<i class="fas fa-notes-medical me-3"></i>Create Medical Record
</a>
</div>
<div class="col-md-6 mb-3">
<a href="{% url 'laboratory:lab_order_create' %}" class="btn btn-outline-warning w-100 text-start">
<i class="fas fa-vial me-3"></i>Order Laboratory Test
</a>
</div>
<div class="col-md-6 mb-3">
<a href="{% url 'billing:bill_create' %}" class="btn btn-outline-danger w-100 text-start">
<i class="fas fa-file-invoice-dollar me-3"></i>Create New Bill
</a>
</div>
<div class="col-md-6 mb-3">
<a href="" class="btn btn-outline-secondary w-100 text-start">
<i class="fas fa-prescription-bottle-alt me-3"></i>Write Prescription
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

View File

@ -33,4 +33,3 @@
<p>No patient notes</p>
</div>
{% endif %}

View File

@ -7,15 +7,36 @@
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-user-plus me-2"></i>Patient Registration
</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>
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
</div>
</div>
<div class="card-body">
<form id="patient-registration-form" method="post">
<div class="panel-body">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
<form id="patient-registration-form" method="post" novalidate>
{% csrf_token %}
<!-- Personal Information -->
@ -23,46 +44,76 @@
<div class="col-12">
<h5 class="border-bottom pb-2">Personal Information</h5>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="first_name" class="form-label">First Name *</label>
<input type="text" class="form-control" id="first_name" name="first_name" required>
<label for="{{ form.first_name.id_for_label }}" class="form-label">First Name *</label>
{{ form.first_name }}
{% if form.first_name.errors %}<div class="invalid-feedback d-block">{{ form.first_name.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="last_name" class="form-label">Last Name *</label>
<input type="text" class="form-control" id="last_name" name="last_name" required>
<label for="{{ form.last_name.id_for_label }}" class="form-label">Last Name *</label>
{{ form.last_name }}
{% if form.last_name.errors %}<div class="invalid-feedback d-block">{{ form.last_name.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="middle_name" class="form-label">Middle Name</label>
<input type="text" class="form-control" id="middle_name" name="middle_name">
<label for="{{ form.middle_name.id_for_label }}" class="form-label">Middle Name</label>
{{ form.middle_name }}
{% if form.middle_name.errors %}<div class="invalid-feedback d-block">{{ form.middle_name.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="date_of_birth" class="form-label">Date of Birth *</label>
<input type="date" class="form-control" id="date_of_birth" name="date_of_birth" required>
<label for="{{ form.preferred_name.id_for_label }}" class="form-label">Preferred Name</label>
{{ form.preferred_name }}
{% if form.preferred_name.errors %}<div class="invalid-feedback d-block">{{ form.preferred_name.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="gender" class="form-label">Gender *</label>
<select class="form-select" id="gender" name="gender" required>
<option value="">Select gender...</option>
<option value="MALE">Male</option>
<option value="FEMALE">Female</option>
<option value="OTHER">Other</option>
<option value="UNKNOWN">Prefer not to say</option>
</select>
<label for="{{ form.suffix.id_for_label }}" class="form-label">Suffix</label>
{{ form.suffix }}
{% if form.suffix.errors %}<div class="invalid-feedback d-block">{{ form.suffix.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="ssn" class="form-label">SSN</label>
<input type="text" class="form-control" id="ssn" name="ssn" placeholder="XXX-XX-XXXX">
<label for="{{ form.id_number.id_for_label }}" class="form-label">ID Number</label>
{{ form.id_number }}
{% if form.id_number.errors %}<div class="invalid-feedback d-block">{{ form.id_number.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.date_of_birth.id_for_label }}" class="form-label">Date of Birth *</label>
{{ form.date_of_birth }}
{% if form.date_of_birth.errors %}<div class="invalid-feedback d-block">{{ form.date_of_birth.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.gender.id_for_label }}" class="form-label">Gender *</label>
{{ form.gender }}
{% if form.gender.errors %}<div class="invalid-feedback d-block">{{ form.gender.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.marital_status.id_for_label }}" class="form-label">Marital Status</label>
{{ form.marital_status }}
{% if form.marital_status.errors %}<div class="invalid-feedback d-block">{{ form.marital_status.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
@ -72,159 +123,129 @@
<div class="col-12">
<h5 class="border-bottom pb-2">Contact Information</h5>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="phone_number" class="form-label">Phone Number</label>
<input type="tel" class="form-control" id="phone_number" name="phone_number">
<label for="{{ form.phone_number.id_for_label }}" class="form-label">Phone Number</label>
{{ form.phone_number }}
{% if form.phone_number.errors %}<div class="invalid-feedback d-block">{{ form.phone_number.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="email" class="form-label">Email Address</label>
<input type="email" class="form-control" id="email" name="email">
<label for="{{ form.mobile_number.id_for_label }}" class="form-label">Mobile Number</label>
{{ form.mobile_number }}
{% if form.mobile_number.errors %}<div class="invalid-feedback d-block">{{ form.mobile_number.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-12">
<div class="col-md-6">
<div class="mb-3">
<label for="address" class="form-label">Address</label>
<textarea class="form-control" id="address" name="address" rows="2"></textarea>
<label for="{{ form.email.id_for_label }}" class="form-label">Email</label>
{{ form.email }}
{% if form.email.errors %}<div class="invalid-feedback d-block">{{ form.email.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<!-- Emergency Contact -->
<!-- Address -->
<div class="row mb-4">
<div class="col-12">
<h5 class="border-bottom pb-2">Emergency Contact</h5>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="emergency_name" class="form-label">Full Name *</label>
<input type="text" class="form-control" id="emergency_name" name="emergency_name" required>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="emergency_relationship" class="form-label">Relationship *</label>
<select class="form-select" id="emergency_relationship" name="emergency_relationship" required>
<option value="">Select relationship...</option>
<option value="SPOUSE">Spouse</option>
<option value="PARENT">Parent</option>
<option value="CHILD">Child</option>
<option value="SIBLING">Sibling</option>
<option value="FRIEND">Friend</option>
<option value="OTHER">Other</option>
</select>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="emergency_phone" class="form-label">Phone Number *</label>
<input type="tel" class="form-control" id="emergency_phone" name="emergency_phone" required>
</div>
</div>
<h5 class="border-bottom pb-2">Address</h5>
</div>
<!-- Insurance Information -->
<div class="row mb-4">
<div class="col-12">
<h5 class="border-bottom pb-2">Insurance Information</h5>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="insurance_provider" class="form-label">Insurance Provider</label>
<input type="text" class="form-control" id="insurance_provider" name="insurance_provider">
<label for="{{ form.address_line_1.id_for_label }}" class="form-label">Address Line 1</label>
{{ form.address_line_1 }}
{% if form.address_line_1.errors %}<div class="invalid-feedback d-block">{{ form.address_line_1.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="insurance_policy_number" class="form-label">Policy Number</label>
<input type="text" class="form-control" id="insurance_policy_number" name="insurance_policy_number">
<label for="{{ form.address_line_2.id_for_label }}" class="form-label">Address Line 2</label>
{{ form.address_line_2 }}
{% if form.address_line_2.errors %}<div class="invalid-feedback d-block">{{ form.address_line_2.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.city.id_for_label }}" class="form-label">City</label>
{{ form.city }}
{% if form.city.errors %}<div class="invalid-feedback d-block">{{ form.city.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.state.id_for_label }}" class="form-label">State/Province</label>
{{ form.state }}
{% if form.state.errors %}<div class="invalid-feedback d-block">{{ form.state.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.zip_code.id_for_label }}" class="form-label">ZIP/Postal Code</label>
{{ form.zip_code }}
{% if form.zip_code.errors %}<div class="invalid-feedback d-block">{{ form.zip_code.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.country.id_for_label }}" class="form-label">Country</label>
{{ form.country }}
{% if form.country.errors %}<div class="invalid-feedback d-block">{{ form.country.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<!-- Consent Forms -->
{% if consent_templates %}
<!-- Other -->
<div class="row mb-4">
<div class="col-12">
<h5 class="border-bottom pb-2">Consent Forms</h5>
{% for template in consent_templates %}
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox"
id="consent_{{ template.id }}"
name="consent_templates"
value="{{ template.id }}"
{% if template.is_required %}required{% endif %}>
<label class="form-check-label" for="consent_{{ template.id }}">
{{ template.name }}
{% if template.is_required %}<span class="text-danger">*</span>{% endif %}
</label>
{% if template.description %}
<div class="form-text">{{ template.description }}</div>
{% endif %}
<h5 class="border-bottom pb-2">Other</h5>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Submit Buttons -->
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.occupation.id_for_label }}" class="form-label">Occupation</label>
{{ form.occupation }}
{% if form.occupation.errors %}<div class="invalid-feedback d-block">{{ form.occupation.errors.0 }}</div>{% endif %}
</div>
</div>
<div class="col-md-6 d-flex align-items-center">
<div class="form-check mb-3">
{{ form.is_active }}
<label class="form-check-label ms-1" for="{{ form.is_active.id_for_label }}">Active</label>
{% if form.is_active.errors %}<div class="invalid-feedback d-block">{{ form.is_active.errors.0 }}</div>{% endif %}
</div>
</div>
</div>
<!-- Actions -->
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-end gap-2">
<button type="button" class="btn btn-secondary">Cancel</button>
<a href="{% url 'patients:patient_list' %}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-1"></i>Register Patient
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div> <!-- /panel-body -->
</div> <!-- /panel -->
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Form validation and submission
document.getElementById('patient-registration-form').addEventListener('submit', function(e) {
e.preventDefault();
// Basic validation
const requiredFields = this.querySelectorAll('[required]');
let isValid = true;
requiredFields.forEach(field => {
if (!field.value.trim()) {
field.classList.add('is-invalid');
isValid = false;
} else {
field.classList.remove('is-invalid');
}
});
if (isValid) {
// Submit form
this.submit();
}
});
// SSN formatting
document.getElementById('ssn').addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
if (value.length >= 6) {
value = value.substring(0,3) + '-' + value.substring(3,5) + '-' + value.substring(5,9);
} else if (value.length >= 4) {
value = value.substring(0,3) + '-' + value.substring(3);
}
e.target.value = value;
});
});
</script>
{% endblock %}
{% block js %}{% endblock %}

View File

@ -382,7 +382,7 @@
<div class="text-center text-muted py-3">
<i class="fas fa-shield-alt fa-2x mb-2"></i>
<p class="small">No insurance information</p>
<a href="{% url 'patients:insurance_form' %}?patient={{ object.pk }}" class="btn btn-sm btn-primary">
<a href="{% url 'patients:insurance_create' object.pk %}" class="btn btn-sm btn-primary">
Add Insurance
</a>
</div>

View File

@ -19,6 +19,7 @@
</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="#" onclick="exportPatients()">
@ -232,7 +233,7 @@
</div>
</td>
<td>
<span class="badge bg-primary">{{ patient.medical_record_number }}</span>
<span class="badge bg-primary">{{ patient.mrn }}</span>
{% if patient.ssn %}
<div class="small text-muted">SSN: ***-**-{{ patient.ssn|slice:"-4:" }}</div>
{% endif %}
@ -284,7 +285,7 @@
title="View Profile">
<i class="fas fa-eye"></i>
</a>
<a href="{% url 'patients:patient_form' patient.pk %}"
<a href="{% url 'patients:patient_update' patient.pk %}"
class="btn btn-outline-secondary"
title="Edit">
<i class="fas fa-edit"></i>
@ -295,7 +296,7 @@
title="Quick Actions">
<i class="fas fa-bolt"></i>
</button>
<a href="{% url 'patients:patient_confirm_delete' patient.pk %}"
<a href="{% url 'patients:patient_delete' patient.pk %}"
class="btn btn-outline-danger"
title="Delete">
<i class="fas fa-trash"></i>
@ -310,38 +311,7 @@
<!-- Pagination -->
{% if is_paginated %}
<div class="card-footer">
<div class="d-flex justify-content-between align-items-center">
<div>
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ paginator.count }} patients
</div>
<nav>
<ul class="pagination pagination-sm mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.gender %}&gender={{ request.GET.gender }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.gender %}&gender={{ request.GET.gender }}{% endif %}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.gender %}&gender={{ request.GET.gender }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.gender %}&gender={{ request.GET.gender }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
{% include 'partial/pagination.html' %}
{% endif %}
<!-- Bulk Actions -->
@ -477,31 +447,31 @@ function quickActions(patientId) {
function scheduleAppointment() {
if (currentPatientId) {
window.location.href = `{% url 'appointments:appointment_form' %}?patient=${currentPatientId}`;
window.location.href = `{% url 'appointments:appointment_create' %}?patient=${currentPatientId}`;
}
}
function newEncounter() {
if (currentPatientId) {
window.location.href = `{% url 'emr:encounter_form' %}?patient=${currentPatientId}`;
window.location.href = `{% url 'emr:encounter_create' %}?patient=${currentPatientId}`;
}
}
function orderLabTest() {
if (currentPatientId) {
window.location.href = `{% url 'laboratory:lab_order_form' %}?patient=${currentPatientId}`;
window.location.href = `{% url 'laboratory:lab_order_create' %}?patient=${currentPatientId}`;
}
}
function newPrescription() {
if (currentPatientId) {
window.location.href = `{% url 'pharmacy:prescription_form' %}?patient=${currentPatientId}`;
window.location.href = `{% url 'pharmacy:prescription_create' %}?patient=${currentPatientId}`;
}
}
function addNote() {
if (currentPatientId) {
window.location.href = `{% url 'patients:patient_note_form' %}?patient=${currentPatientId}`;
window.location.href = `{% url 'patients:patient_note_create' 0 %}`.replace('0', currentPatientId)
}
}