+
-
- {% if test_result %}
-
-
-
+
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+
+
- {% endif %}
-
-
-
-
-
-
-
- All Data Sources
-
-
- Add Data Source
-
+
+ {% if test_result %}
+
+
+
+
+
Status:
+
+ {% if test_result.success %}
+ Connected
+ {% else %}
+ Failed
+ {% endif %}
+
+
+
Response Time:
+
{{ test_result.response_time }}ms
+
+
+ {% if test_result.message %}
+
+
Message:
+
{{ test_result.message }}
+
+ {% endif %}
+
+ {% if test_result.data %}
+
+
Sample Data:
+
+
{{ test_result.data|truncatechars:500 }}
+
+
+ {% endif %}
+
+
+ {% endif %}
+
+
+
@@ -135,6 +134,7 @@
+
{% endblock %}
{% block js %}
diff --git a/analytics_data.py b/analytics_data.py
index a2ef91a3..f6eabb4c 100644
--- a/analytics_data.py
+++ b/analytics_data.py
@@ -56,7 +56,7 @@ def generate_dashboards(tenants, users):
created_by=creator
)
dashboard.allowed_users.add(creator)
- dashboard.allowed_roles = [creator.role]
+ dashboard.allowed_roles = [creator.employee_profile.role]
dashboard.save()
dashboards.append(dashboard)
print(f"✅ Created {len(dashboards)} dashboards")
diff --git a/appointments/__pycache__/forms.cpython-312.pyc b/appointments/__pycache__/forms.cpython-312.pyc
index 0b8962c8..400cf857 100644
Binary files a/appointments/__pycache__/forms.cpython-312.pyc and b/appointments/__pycache__/forms.cpython-312.pyc differ
diff --git a/appointments/__pycache__/models.cpython-312.pyc b/appointments/__pycache__/models.cpython-312.pyc
index d253311d..f205c1a8 100644
Binary files a/appointments/__pycache__/models.cpython-312.pyc and b/appointments/__pycache__/models.cpython-312.pyc differ
diff --git a/appointments/__pycache__/urls.cpython-312.pyc b/appointments/__pycache__/urls.cpython-312.pyc
index a08b1834..e6d4a5ca 100644
Binary files a/appointments/__pycache__/urls.cpython-312.pyc and b/appointments/__pycache__/urls.cpython-312.pyc differ
diff --git a/appointments/__pycache__/views.cpython-312.pyc b/appointments/__pycache__/views.cpython-312.pyc
index 0c64ffa1..41f6326c 100644
Binary files a/appointments/__pycache__/views.cpython-312.pyc and b/appointments/__pycache__/views.cpython-312.pyc differ
diff --git a/appointments/api/__pycache__/__init__.cpython-312.pyc b/appointments/api/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 00000000..c5a64f98
Binary files /dev/null and b/appointments/api/__pycache__/__init__.cpython-312.pyc differ
diff --git a/appointments/api/__pycache__/serializers.cpython-312.pyc b/appointments/api/__pycache__/serializers.cpython-312.pyc
new file mode 100644
index 00000000..5980ff65
Binary files /dev/null and b/appointments/api/__pycache__/serializers.cpython-312.pyc differ
diff --git a/appointments/api/__pycache__/urls.cpython-312.pyc b/appointments/api/__pycache__/urls.cpython-312.pyc
new file mode 100644
index 00000000..854e9104
Binary files /dev/null and b/appointments/api/__pycache__/urls.cpython-312.pyc differ
diff --git a/appointments/api/__pycache__/views.cpython-312.pyc b/appointments/api/__pycache__/views.cpython-312.pyc
new file mode 100644
index 00000000..70bf1a35
Binary files /dev/null and b/appointments/api/__pycache__/views.cpython-312.pyc differ
diff --git a/appointments/forms.py b/appointments/forms.py
index dc0514ee..cbf2e79d 100644
--- a/appointments/forms.py
+++ b/appointments/forms.py
@@ -353,7 +353,7 @@ class AppointmentSearchForm(forms.Form):
self.fields['provider'].queryset = User.objects.filter(
tenant=user.tenant,
is_active=True,
- role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
+ employee_profile__role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
).order_by('last_name', 'first_name')
@@ -645,34 +645,34 @@ class WaitingListContactLogForm(forms.ModelForm):
widgets = {
'contact_method': forms.Select(attrs={
- 'class': 'form-select',
+ 'class': 'form-select form-select-sm',
'required': True
}),
'contact_outcome': forms.Select(attrs={
- 'class': 'form-select',
+ 'class': 'form-select form-select-sm',
'required': True
}),
'appointment_offered': forms.CheckboxInput(attrs={
'class': 'form-check-input'
}),
'offered_date': forms.DateInput(attrs={
- 'class': 'form-control',
+ 'class': 'form-control form-control-sm',
'type': 'date'
}),
'offered_time': forms.TimeInput(attrs={
- 'class': 'form-control',
+ 'class': 'form-control form-control-sm',
'type': 'time'
}),
'patient_response': forms.Select(attrs={
- 'class': 'form-select'
+ 'class': 'form-select form-select-sm'
}),
'notes': forms.Textarea(attrs={
- 'class': 'form-control',
+ 'class': 'form-control form-control-sm',
'rows': 4,
'placeholder': 'Notes from contact attempt...'
}),
'next_contact_date': forms.DateInput(attrs={
- 'class': 'form-control',
+ 'class': 'form-control form-control-sm',
'type': 'date',
'min': date.today().isoformat()
}),
diff --git a/appointments/migrations/0001_initial.py b/appointments/migrations/0001_initial.py
index bfb983fd..44f2b65a 100644
--- a/appointments/migrations/0001_initial.py
+++ b/appointments/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.2.6 on 2025-09-08 07:28
+# Generated by Django 5.2.6 on 2025-09-15 14:05
import django.core.validators
import django.db.models.deletion
@@ -549,6 +549,526 @@ class Migration(migrations.Migration):
"ordering": ["-scheduled_start"],
},
),
+ migrations.CreateModel(
+ name="WaitingList",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "waiting_list_id",
+ models.UUIDField(
+ default=uuid.uuid4,
+ editable=False,
+ help_text="Unique waiting list entry identifier",
+ unique=True,
+ ),
+ ),
+ (
+ "appointment_type",
+ models.CharField(
+ choices=[
+ ("CONSULTATION", "Consultation"),
+ ("FOLLOW_UP", "Follow-up"),
+ ("PROCEDURE", "Procedure"),
+ ("SURGERY", "Surgery"),
+ ("DIAGNOSTIC", "Diagnostic"),
+ ("THERAPY", "Therapy"),
+ ("VACCINATION", "Vaccination"),
+ ("SCREENING", "Screening"),
+ ("EMERGENCY", "Emergency"),
+ ("TELEMEDICINE", "Telemedicine"),
+ ("OTHER", "Other"),
+ ],
+ help_text="Type of appointment requested",
+ max_length=50,
+ ),
+ ),
+ (
+ "specialty",
+ models.CharField(
+ choices=[
+ ("FAMILY_MEDICINE", "Family Medicine"),
+ ("INTERNAL_MEDICINE", "Internal Medicine"),
+ ("PEDIATRICS", "Pediatrics"),
+ ("CARDIOLOGY", "Cardiology"),
+ ("DERMATOLOGY", "Dermatology"),
+ ("ENDOCRINOLOGY", "Endocrinology"),
+ ("GASTROENTEROLOGY", "Gastroenterology"),
+ ("NEUROLOGY", "Neurology"),
+ ("ONCOLOGY", "Oncology"),
+ ("ORTHOPEDICS", "Orthopedics"),
+ ("PSYCHIATRY", "Psychiatry"),
+ ("RADIOLOGY", "Radiology"),
+ ("SURGERY", "Surgery"),
+ ("UROLOGY", "Urology"),
+ ("GYNECOLOGY", "Gynecology"),
+ ("OPHTHALMOLOGY", "Ophthalmology"),
+ ("ENT", "Ear, Nose & Throat"),
+ ("EMERGENCY", "Emergency Medicine"),
+ ("OTHER", "Other"),
+ ],
+ help_text="Medical specialty required",
+ max_length=100,
+ ),
+ ),
+ (
+ "priority",
+ models.CharField(
+ choices=[
+ ("ROUTINE", "Routine"),
+ ("URGENT", "Urgent"),
+ ("STAT", "STAT"),
+ ("EMERGENCY", "Emergency"),
+ ],
+ default="ROUTINE",
+ help_text="Clinical priority level",
+ max_length=20,
+ ),
+ ),
+ (
+ "urgency_score",
+ models.PositiveIntegerField(
+ default=1,
+ help_text="Clinical urgency score (1-10, 10 being most urgent)",
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(10),
+ ],
+ ),
+ ),
+ (
+ "clinical_indication",
+ models.TextField(
+ help_text="Clinical reason for appointment request"
+ ),
+ ),
+ (
+ "diagnosis_codes",
+ models.JSONField(
+ blank=True, default=list, help_text="ICD-10 diagnosis codes"
+ ),
+ ),
+ (
+ "preferred_date",
+ models.DateField(
+ blank=True,
+ help_text="Patient preferred appointment date",
+ null=True,
+ ),
+ ),
+ (
+ "preferred_time",
+ models.TimeField(
+ blank=True,
+ help_text="Patient preferred appointment time",
+ null=True,
+ ),
+ ),
+ (
+ "flexible_scheduling",
+ models.BooleanField(
+ default=True,
+ help_text="Patient accepts alternative dates/times",
+ ),
+ ),
+ (
+ "earliest_acceptable_date",
+ models.DateField(
+ blank=True,
+ help_text="Earliest acceptable appointment date",
+ null=True,
+ ),
+ ),
+ (
+ "latest_acceptable_date",
+ models.DateField(
+ blank=True,
+ help_text="Latest acceptable appointment date",
+ null=True,
+ ),
+ ),
+ (
+ "acceptable_days",
+ models.JSONField(
+ blank=True,
+ default=list,
+ help_text="Acceptable days of week (0=Monday, 6=Sunday)",
+ ),
+ ),
+ (
+ "acceptable_times",
+ models.JSONField(
+ blank=True, default=list, help_text="Acceptable time ranges"
+ ),
+ ),
+ (
+ "contact_method",
+ models.CharField(
+ choices=[
+ ("PHONE", "Phone"),
+ ("EMAIL", "Email"),
+ ("SMS", "SMS"),
+ ("PORTAL", "Patient Portal"),
+ ("MAIL", "Mail"),
+ ],
+ default="PHONE",
+ help_text="Preferred contact method",
+ max_length=20,
+ ),
+ ),
+ (
+ "contact_phone",
+ models.CharField(
+ blank=True,
+ help_text="Contact phone number",
+ max_length=20,
+ null=True,
+ ),
+ ),
+ (
+ "contact_email",
+ models.EmailField(
+ blank=True,
+ help_text="Contact email address",
+ max_length=254,
+ null=True,
+ ),
+ ),
+ (
+ "status",
+ models.CharField(
+ choices=[
+ ("ACTIVE", "Active"),
+ ("CONTACTED", "Contacted"),
+ ("OFFERED", "Appointment Offered"),
+ ("SCHEDULED", "Scheduled"),
+ ("CANCELLED", "Cancelled"),
+ ("EXPIRED", "Expired"),
+ ("TRANSFERRED", "Transferred"),
+ ],
+ default="ACTIVE",
+ help_text="Waiting list status",
+ max_length=20,
+ ),
+ ),
+ (
+ "position",
+ models.PositiveIntegerField(
+ blank=True,
+ help_text="Position in waiting list queue",
+ null=True,
+ ),
+ ),
+ (
+ "estimated_wait_time",
+ models.PositiveIntegerField(
+ blank=True, help_text="Estimated wait time in days", null=True
+ ),
+ ),
+ (
+ "last_contacted",
+ models.DateTimeField(
+ blank=True,
+ help_text="Last contact attempt date/time",
+ null=True,
+ ),
+ ),
+ (
+ "contact_attempts",
+ models.PositiveIntegerField(
+ default=0, help_text="Number of contact attempts made"
+ ),
+ ),
+ (
+ "max_contact_attempts",
+ models.PositiveIntegerField(
+ default=3, help_text="Maximum contact attempts before expiring"
+ ),
+ ),
+ (
+ "appointments_offered",
+ models.PositiveIntegerField(
+ default=0, help_text="Number of appointments offered"
+ ),
+ ),
+ (
+ "appointments_declined",
+ models.PositiveIntegerField(
+ default=0, help_text="Number of appointments declined"
+ ),
+ ),
+ (
+ "last_offer_date",
+ models.DateTimeField(
+ blank=True,
+ help_text="Date of last appointment offer",
+ null=True,
+ ),
+ ),
+ (
+ "requires_interpreter",
+ models.BooleanField(
+ default=False, help_text="Patient requires interpreter services"
+ ),
+ ),
+ (
+ "interpreter_language",
+ models.CharField(
+ blank=True,
+ help_text="Required interpreter language",
+ max_length=50,
+ null=True,
+ ),
+ ),
+ (
+ "accessibility_requirements",
+ models.TextField(
+ blank=True,
+ help_text="Special accessibility requirements",
+ null=True,
+ ),
+ ),
+ (
+ "transportation_needed",
+ models.BooleanField(
+ default=False,
+ help_text="Patient needs transportation assistance",
+ ),
+ ),
+ (
+ "insurance_verified",
+ models.BooleanField(
+ default=False, help_text="Insurance coverage verified"
+ ),
+ ),
+ (
+ "authorization_required",
+ models.BooleanField(
+ default=False, help_text="Prior authorization required"
+ ),
+ ),
+ (
+ "authorization_status",
+ models.CharField(
+ choices=[
+ ("NOT_REQUIRED", "Not Required"),
+ ("PENDING", "Pending"),
+ ("APPROVED", "Approved"),
+ ("DENIED", "Denied"),
+ ("EXPIRED", "Expired"),
+ ],
+ default="NOT_REQUIRED",
+ help_text="Authorization status",
+ max_length=20,
+ ),
+ ),
+ (
+ "authorization_number",
+ models.CharField(
+ blank=True,
+ help_text="Authorization number",
+ max_length=100,
+ null=True,
+ ),
+ ),
+ (
+ "referring_provider",
+ models.CharField(
+ blank=True,
+ help_text="Referring provider name",
+ max_length=200,
+ null=True,
+ ),
+ ),
+ (
+ "referral_date",
+ models.DateField(
+ blank=True, help_text="Date of referral", null=True
+ ),
+ ),
+ (
+ "referral_urgency",
+ models.CharField(
+ choices=[
+ ("ROUTINE", "Routine"),
+ ("URGENT", "Urgent"),
+ ("STAT", "STAT"),
+ ],
+ default="ROUTINE",
+ help_text="Referral urgency level",
+ max_length=20,
+ ),
+ ),
+ (
+ "removal_reason",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("SCHEDULED", "Appointment Scheduled"),
+ ("PATIENT_CANCELLED", "Patient Cancelled"),
+ ("PROVIDER_CANCELLED", "Provider Cancelled"),
+ ("NO_RESPONSE", "No Response to Contact"),
+ ("INSURANCE_ISSUE", "Insurance Issue"),
+ ("TRANSFERRED", "Transferred to Another Provider"),
+ ("EXPIRED", "Entry Expired"),
+ ("DUPLICATE", "Duplicate Entry"),
+ ("OTHER", "Other"),
+ ],
+ help_text="Reason for removal from waiting list",
+ max_length=50,
+ null=True,
+ ),
+ ),
+ (
+ "removal_notes",
+ models.TextField(
+ blank=True,
+ help_text="Additional notes about removal",
+ null=True,
+ ),
+ ),
+ (
+ "removed_at",
+ models.DateTimeField(
+ blank=True,
+ help_text="Date/time removed from waiting list",
+ null=True,
+ ),
+ ),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
+ ("updated_at", models.DateTimeField(auto_now=True)),
+ (
+ "notes",
+ models.TextField(
+ blank=True, help_text="Additional notes and comments", null=True
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "Waiting List Entry",
+ "verbose_name_plural": "Waiting List Entries",
+ "db_table": "appointments_waiting_list",
+ "ordering": ["priority", "urgency_score", "created_at"],
+ },
+ ),
+ migrations.CreateModel(
+ name="WaitingListContactLog",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "contact_date",
+ models.DateTimeField(
+ auto_now_add=True, help_text="Date and time of contact attempt"
+ ),
+ ),
+ (
+ "contact_method",
+ models.CharField(
+ choices=[
+ ("PHONE", "Phone Call"),
+ ("EMAIL", "Email"),
+ ("SMS", "SMS"),
+ ("PORTAL", "Patient Portal Message"),
+ ("MAIL", "Mail"),
+ ("IN_PERSON", "In Person"),
+ ],
+ help_text="Method of contact used",
+ max_length=20,
+ ),
+ ),
+ (
+ "contact_outcome",
+ models.CharField(
+ choices=[
+ ("SUCCESSFUL", "Successful Contact"),
+ ("NO_ANSWER", "No Answer"),
+ ("BUSY", "Line Busy"),
+ ("VOICEMAIL", "Left Voicemail"),
+ ("EMAIL_SENT", "Email Sent"),
+ ("EMAIL_BOUNCED", "Email Bounced"),
+ ("SMS_SENT", "SMS Sent"),
+ ("SMS_FAILED", "SMS Failed"),
+ ("WRONG_NUMBER", "Wrong Number"),
+ ("DECLINED", "Patient Declined"),
+ ],
+ help_text="Outcome of contact attempt",
+ max_length=20,
+ ),
+ ),
+ (
+ "appointment_offered",
+ models.BooleanField(
+ default=False,
+ help_text="Appointment was offered during contact",
+ ),
+ ),
+ (
+ "offered_date",
+ models.DateField(
+ blank=True, help_text="Date of offered appointment", null=True
+ ),
+ ),
+ (
+ "offered_time",
+ models.TimeField(
+ blank=True, help_text="Time of offered appointment", null=True
+ ),
+ ),
+ (
+ "patient_response",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("ACCEPTED", "Accepted Appointment"),
+ ("DECLINED", "Declined Appointment"),
+ ("REQUESTED_DIFFERENT", "Requested Different Time"),
+ ("WILL_CALL_BACK", "Will Call Back"),
+ ("NO_LONGER_NEEDED", "No Longer Needed"),
+ ("INSURANCE_ISSUE", "Insurance Issue"),
+ ("NO_RESPONSE", "No Response"),
+ ],
+ help_text="Patient response to contact",
+ max_length=20,
+ null=True,
+ ),
+ ),
+ (
+ "notes",
+ models.TextField(
+ blank=True, help_text="Notes from contact attempt", null=True
+ ),
+ ),
+ (
+ "next_contact_date",
+ models.DateField(
+ blank=True,
+ help_text="Scheduled date for next contact attempt",
+ null=True,
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "Waiting List Contact Log",
+ "verbose_name_plural": "Waiting List Contact Logs",
+ "db_table": "appointments_waiting_list_contact_log",
+ "ordering": ["-contact_date"],
+ },
+ ),
migrations.CreateModel(
name="WaitingQueue",
fields=[
diff --git a/appointments/migrations/0002_initial.py b/appointments/migrations/0002_initial.py
index cc81bb00..5cdfc5c7 100644
--- a/appointments/migrations/0002_initial.py
+++ b/appointments/migrations/0002_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.2.6 on 2025-09-08 07:28
+# Generated by Django 5.2.6 on 2025-09-15 14:05
import django.db.models.deletion
from django.conf import settings
@@ -12,6 +12,7 @@ class Migration(migrations.Migration):
dependencies = [
("appointments", "0001_initial"),
("core", "0001_initial"),
+ ("hr", "0001_initial"),
("patients", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
@@ -179,6 +180,105 @@ class Migration(migrations.Migration):
to=settings.AUTH_USER_MODEL,
),
),
+ migrations.AddField(
+ model_name="waitinglist",
+ name="created_by",
+ field=models.ForeignKey(
+ blank=True,
+ help_text="User who created the waiting list entry",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="created_waiting_list_entries",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ migrations.AddField(
+ model_name="waitinglist",
+ name="department",
+ field=models.ForeignKey(
+ help_text="Department for appointment",
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="waiting_list_entries",
+ to="hr.department",
+ ),
+ ),
+ migrations.AddField(
+ model_name="waitinglist",
+ name="patient",
+ field=models.ForeignKey(
+ help_text="Patient on waiting list",
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="waiting_list_entries",
+ to="patients.patientprofile",
+ ),
+ ),
+ migrations.AddField(
+ model_name="waitinglist",
+ name="provider",
+ field=models.ForeignKey(
+ blank=True,
+ help_text="Preferred healthcare provider",
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="provider_waiting_list",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ migrations.AddField(
+ model_name="waitinglist",
+ name="removed_by",
+ field=models.ForeignKey(
+ blank=True,
+ help_text="User who removed entry from waiting list",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="removed_waiting_list_entries",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ migrations.AddField(
+ model_name="waitinglist",
+ name="scheduled_appointment",
+ field=models.ForeignKey(
+ blank=True,
+ help_text="Scheduled appointment from waiting list",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="waiting_list_entry",
+ to="appointments.appointmentrequest",
+ ),
+ ),
+ migrations.AddField(
+ model_name="waitinglist",
+ name="tenant",
+ field=models.ForeignKey(
+ help_text="Organization tenant",
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="waiting_list_entries",
+ to="core.tenant",
+ ),
+ ),
+ migrations.AddField(
+ model_name="waitinglistcontactlog",
+ name="contacted_by",
+ field=models.ForeignKey(
+ blank=True,
+ help_text="Staff member who made contact",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ migrations.AddField(
+ model_name="waitinglistcontactlog",
+ name="waiting_list_entry",
+ field=models.ForeignKey(
+ help_text="Associated waiting list entry",
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="contact_logs",
+ to="appointments.waitinglist",
+ ),
+ ),
migrations.AddField(
model_name="waitingqueue",
name="created_by",
@@ -324,6 +424,63 @@ class Migration(migrations.Migration):
fields=["scheduled_start"], name="appointment_schedul_8a4e8e_idx"
),
),
+ migrations.AddIndex(
+ model_name="waitinglist",
+ index=models.Index(
+ fields=["tenant", "status"], name="appointment_tenant__a558da_idx"
+ ),
+ ),
+ migrations.AddIndex(
+ model_name="waitinglist",
+ index=models.Index(
+ fields=["patient", "status"], name="appointment_patient_73f03d_idx"
+ ),
+ ),
+ migrations.AddIndex(
+ model_name="waitinglist",
+ index=models.Index(
+ fields=["department", "specialty", "status"],
+ name="appointment_departm_78fd70_idx",
+ ),
+ ),
+ migrations.AddIndex(
+ model_name="waitinglist",
+ index=models.Index(
+ fields=["priority", "urgency_score"],
+ name="appointment_priorit_30fb90_idx",
+ ),
+ ),
+ migrations.AddIndex(
+ model_name="waitinglist",
+ index=models.Index(
+ fields=["status", "created_at"], name="appointment_status_cfe551_idx"
+ ),
+ ),
+ migrations.AddIndex(
+ model_name="waitinglist",
+ index=models.Index(
+ fields=["provider", "status"], name="appointment_provide_dd6c2b_idx"
+ ),
+ ),
+ migrations.AddIndex(
+ model_name="waitinglistcontactlog",
+ index=models.Index(
+ fields=["waiting_list_entry", "contact_date"],
+ name="appointment_waiting_50d8ac_idx",
+ ),
+ ),
+ migrations.AddIndex(
+ model_name="waitinglistcontactlog",
+ index=models.Index(
+ fields=["contact_outcome"], name="appointment_contact_ad9c45_idx"
+ ),
+ ),
+ migrations.AddIndex(
+ model_name="waitinglistcontactlog",
+ index=models.Index(
+ fields=["next_contact_date"], name="appointment_next_co_b29984_idx"
+ ),
+ ),
migrations.AddIndex(
model_name="waitingqueue",
index=models.Index(
diff --git a/appointments/migrations/0003_alter_waitinglist_acceptable_days.py b/appointments/migrations/0003_alter_waitinglist_acceptable_days.py
new file mode 100644
index 00000000..15ce0c1a
--- /dev/null
+++ b/appointments/migrations/0003_alter_waitinglist_acceptable_days.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.2.6 on 2025-09-16 12:04
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("appointments", "0002_initial"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="waitinglist",
+ name="acceptable_days",
+ field=models.JSONField(
+ blank=True,
+ default=list,
+ help_text="Acceptable days of week (0=Monday, 6=Sunday)",
+ null=True,
+ ),
+ ),
+ ]
diff --git a/appointments/migrations/0003_waitinglist_waitinglistcontactlog_and_more.py b/appointments/migrations/0003_waitinglist_waitinglistcontactlog_and_more.py
deleted file mode 100644
index 7b3fa2ed..00000000
--- a/appointments/migrations/0003_waitinglist_waitinglistcontactlog_and_more.py
+++ /dev/null
@@ -1,688 +0,0 @@
-# Generated by Django 5.2.6 on 2025-09-11 17:03
-
-import django.core.validators
-import django.db.models.deletion
-import uuid
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("appointments", "0002_initial"),
- ("core", "0001_initial"),
- ("hr", "0001_initial"),
- ("patients", "0003_remove_insuranceinfo_subscriber_ssn_and_more"),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.CreateModel(
- name="WaitingList",
- fields=[
- (
- "id",
- models.BigAutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name="ID",
- ),
- ),
- (
- "waiting_list_id",
- models.UUIDField(
- default=uuid.uuid4,
- editable=False,
- help_text="Unique waiting list entry identifier",
- unique=True,
- ),
- ),
- (
- "appointment_type",
- models.CharField(
- choices=[
- ("CONSULTATION", "Consultation"),
- ("FOLLOW_UP", "Follow-up"),
- ("PROCEDURE", "Procedure"),
- ("SURGERY", "Surgery"),
- ("DIAGNOSTIC", "Diagnostic"),
- ("THERAPY", "Therapy"),
- ("VACCINATION", "Vaccination"),
- ("SCREENING", "Screening"),
- ("EMERGENCY", "Emergency"),
- ("TELEMEDICINE", "Telemedicine"),
- ("OTHER", "Other"),
- ],
- help_text="Type of appointment requested",
- max_length=50,
- ),
- ),
- (
- "specialty",
- models.CharField(
- choices=[
- ("FAMILY_MEDICINE", "Family Medicine"),
- ("INTERNAL_MEDICINE", "Internal Medicine"),
- ("PEDIATRICS", "Pediatrics"),
- ("CARDIOLOGY", "Cardiology"),
- ("DERMATOLOGY", "Dermatology"),
- ("ENDOCRINOLOGY", "Endocrinology"),
- ("GASTROENTEROLOGY", "Gastroenterology"),
- ("NEUROLOGY", "Neurology"),
- ("ONCOLOGY", "Oncology"),
- ("ORTHOPEDICS", "Orthopedics"),
- ("PSYCHIATRY", "Psychiatry"),
- ("RADIOLOGY", "Radiology"),
- ("SURGERY", "Surgery"),
- ("UROLOGY", "Urology"),
- ("GYNECOLOGY", "Gynecology"),
- ("OPHTHALMOLOGY", "Ophthalmology"),
- ("ENT", "Ear, Nose & Throat"),
- ("EMERGENCY", "Emergency Medicine"),
- ("OTHER", "Other"),
- ],
- help_text="Medical specialty required",
- max_length=100,
- ),
- ),
- (
- "priority",
- models.CharField(
- choices=[
- ("ROUTINE", "Routine"),
- ("URGENT", "Urgent"),
- ("STAT", "STAT"),
- ("EMERGENCY", "Emergency"),
- ],
- default="ROUTINE",
- help_text="Clinical priority level",
- max_length=20,
- ),
- ),
- (
- "urgency_score",
- models.PositiveIntegerField(
- default=1,
- help_text="Clinical urgency score (1-10, 10 being most urgent)",
- validators=[
- django.core.validators.MinValueValidator(1),
- django.core.validators.MaxValueValidator(10),
- ],
- ),
- ),
- (
- "clinical_indication",
- models.TextField(
- help_text="Clinical reason for appointment request"
- ),
- ),
- (
- "diagnosis_codes",
- models.JSONField(
- blank=True, default=list, help_text="ICD-10 diagnosis codes"
- ),
- ),
- (
- "preferred_date",
- models.DateField(
- blank=True,
- help_text="Patient preferred appointment date",
- null=True,
- ),
- ),
- (
- "preferred_time",
- models.TimeField(
- blank=True,
- help_text="Patient preferred appointment time",
- null=True,
- ),
- ),
- (
- "flexible_scheduling",
- models.BooleanField(
- default=True,
- help_text="Patient accepts alternative dates/times",
- ),
- ),
- (
- "earliest_acceptable_date",
- models.DateField(
- blank=True,
- help_text="Earliest acceptable appointment date",
- null=True,
- ),
- ),
- (
- "latest_acceptable_date",
- models.DateField(
- blank=True,
- help_text="Latest acceptable appointment date",
- null=True,
- ),
- ),
- (
- "acceptable_days",
- models.JSONField(
- blank=True,
- default=list,
- help_text="Acceptable days of week (0=Monday, 6=Sunday)",
- ),
- ),
- (
- "acceptable_times",
- models.JSONField(
- blank=True, default=list, help_text="Acceptable time ranges"
- ),
- ),
- (
- "contact_method",
- models.CharField(
- choices=[
- ("PHONE", "Phone"),
- ("EMAIL", "Email"),
- ("SMS", "SMS"),
- ("PORTAL", "Patient Portal"),
- ("MAIL", "Mail"),
- ],
- default="PHONE",
- help_text="Preferred contact method",
- max_length=20,
- ),
- ),
- (
- "contact_phone",
- models.CharField(
- blank=True,
- help_text="Contact phone number",
- max_length=20,
- null=True,
- ),
- ),
- (
- "contact_email",
- models.EmailField(
- blank=True,
- help_text="Contact email address",
- max_length=254,
- null=True,
- ),
- ),
- (
- "status",
- models.CharField(
- choices=[
- ("ACTIVE", "Active"),
- ("CONTACTED", "Contacted"),
- ("OFFERED", "Appointment Offered"),
- ("SCHEDULED", "Scheduled"),
- ("CANCELLED", "Cancelled"),
- ("EXPIRED", "Expired"),
- ("TRANSFERRED", "Transferred"),
- ],
- default="ACTIVE",
- help_text="Waiting list status",
- max_length=20,
- ),
- ),
- (
- "position",
- models.PositiveIntegerField(
- blank=True,
- help_text="Position in waiting list queue",
- null=True,
- ),
- ),
- (
- "estimated_wait_time",
- models.PositiveIntegerField(
- blank=True, help_text="Estimated wait time in days", null=True
- ),
- ),
- (
- "last_contacted",
- models.DateTimeField(
- blank=True,
- help_text="Last contact attempt date/time",
- null=True,
- ),
- ),
- (
- "contact_attempts",
- models.PositiveIntegerField(
- default=0, help_text="Number of contact attempts made"
- ),
- ),
- (
- "max_contact_attempts",
- models.PositiveIntegerField(
- default=3, help_text="Maximum contact attempts before expiring"
- ),
- ),
- (
- "appointments_offered",
- models.PositiveIntegerField(
- default=0, help_text="Number of appointments offered"
- ),
- ),
- (
- "appointments_declined",
- models.PositiveIntegerField(
- default=0, help_text="Number of appointments declined"
- ),
- ),
- (
- "last_offer_date",
- models.DateTimeField(
- blank=True,
- help_text="Date of last appointment offer",
- null=True,
- ),
- ),
- (
- "requires_interpreter",
- models.BooleanField(
- default=False, help_text="Patient requires interpreter services"
- ),
- ),
- (
- "interpreter_language",
- models.CharField(
- blank=True,
- help_text="Required interpreter language",
- max_length=50,
- null=True,
- ),
- ),
- (
- "accessibility_requirements",
- models.TextField(
- blank=True,
- help_text="Special accessibility requirements",
- null=True,
- ),
- ),
- (
- "transportation_needed",
- models.BooleanField(
- default=False,
- help_text="Patient needs transportation assistance",
- ),
- ),
- (
- "insurance_verified",
- models.BooleanField(
- default=False, help_text="Insurance coverage verified"
- ),
- ),
- (
- "authorization_required",
- models.BooleanField(
- default=False, help_text="Prior authorization required"
- ),
- ),
- (
- "authorization_status",
- models.CharField(
- choices=[
- ("NOT_REQUIRED", "Not Required"),
- ("PENDING", "Pending"),
- ("APPROVED", "Approved"),
- ("DENIED", "Denied"),
- ("EXPIRED", "Expired"),
- ],
- default="NOT_REQUIRED",
- help_text="Authorization status",
- max_length=20,
- ),
- ),
- (
- "authorization_number",
- models.CharField(
- blank=True,
- help_text="Authorization number",
- max_length=100,
- null=True,
- ),
- ),
- (
- "referring_provider",
- models.CharField(
- blank=True,
- help_text="Referring provider name",
- max_length=200,
- null=True,
- ),
- ),
- (
- "referral_date",
- models.DateField(
- blank=True, help_text="Date of referral", null=True
- ),
- ),
- (
- "referral_urgency",
- models.CharField(
- choices=[
- ("ROUTINE", "Routine"),
- ("URGENT", "Urgent"),
- ("STAT", "STAT"),
- ],
- default="ROUTINE",
- help_text="Referral urgency level",
- max_length=20,
- ),
- ),
- (
- "removal_reason",
- models.CharField(
- blank=True,
- choices=[
- ("SCHEDULED", "Appointment Scheduled"),
- ("PATIENT_CANCELLED", "Patient Cancelled"),
- ("PROVIDER_CANCELLED", "Provider Cancelled"),
- ("NO_RESPONSE", "No Response to Contact"),
- ("INSURANCE_ISSUE", "Insurance Issue"),
- ("TRANSFERRED", "Transferred to Another Provider"),
- ("EXPIRED", "Entry Expired"),
- ("DUPLICATE", "Duplicate Entry"),
- ("OTHER", "Other"),
- ],
- help_text="Reason for removal from waiting list",
- max_length=50,
- null=True,
- ),
- ),
- (
- "removal_notes",
- models.TextField(
- blank=True,
- help_text="Additional notes about removal",
- null=True,
- ),
- ),
- (
- "removed_at",
- models.DateTimeField(
- blank=True,
- help_text="Date/time removed from waiting list",
- null=True,
- ),
- ),
- ("created_at", models.DateTimeField(auto_now_add=True)),
- ("updated_at", models.DateTimeField(auto_now=True)),
- (
- "notes",
- models.TextField(
- blank=True, help_text="Additional notes and comments", null=True
- ),
- ),
- (
- "created_by",
- models.ForeignKey(
- blank=True,
- help_text="User who created the waiting list entry",
- null=True,
- on_delete=django.db.models.deletion.SET_NULL,
- related_name="created_waiting_list_entries",
- to=settings.AUTH_USER_MODEL,
- ),
- ),
- (
- "department",
- models.ForeignKey(
- help_text="Department for appointment",
- on_delete=django.db.models.deletion.CASCADE,
- related_name="waiting_list_entries",
- to="hr.department",
- ),
- ),
- (
- "patient",
- models.ForeignKey(
- help_text="Patient on waiting list",
- on_delete=django.db.models.deletion.CASCADE,
- related_name="waiting_list_entries",
- to="patients.patientprofile",
- ),
- ),
- (
- "provider",
- models.ForeignKey(
- blank=True,
- help_text="Preferred healthcare provider",
- null=True,
- on_delete=django.db.models.deletion.CASCADE,
- related_name="provider_waiting_list",
- to=settings.AUTH_USER_MODEL,
- ),
- ),
- (
- "removed_by",
- models.ForeignKey(
- blank=True,
- help_text="User who removed entry from waiting list",
- null=True,
- on_delete=django.db.models.deletion.SET_NULL,
- related_name="removed_waiting_list_entries",
- to=settings.AUTH_USER_MODEL,
- ),
- ),
- (
- "scheduled_appointment",
- models.ForeignKey(
- blank=True,
- help_text="Scheduled appointment from waiting list",
- null=True,
- on_delete=django.db.models.deletion.SET_NULL,
- related_name="waiting_list_entry",
- to="appointments.appointmentrequest",
- ),
- ),
- (
- "tenant",
- models.ForeignKey(
- help_text="Organization tenant",
- on_delete=django.db.models.deletion.CASCADE,
- related_name="waiting_list_entries",
- to="core.tenant",
- ),
- ),
- ],
- options={
- "verbose_name": "Waiting List Entry",
- "verbose_name_plural": "Waiting List Entries",
- "db_table": "appointments_waiting_list",
- "ordering": ["priority", "urgency_score", "created_at"],
- },
- ),
- migrations.CreateModel(
- name="WaitingListContactLog",
- fields=[
- (
- "id",
- models.BigAutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name="ID",
- ),
- ),
- (
- "contact_date",
- models.DateTimeField(
- auto_now_add=True, help_text="Date and time of contact attempt"
- ),
- ),
- (
- "contact_method",
- models.CharField(
- choices=[
- ("PHONE", "Phone Call"),
- ("EMAIL", "Email"),
- ("SMS", "SMS"),
- ("PORTAL", "Patient Portal Message"),
- ("MAIL", "Mail"),
- ("IN_PERSON", "In Person"),
- ],
- help_text="Method of contact used",
- max_length=20,
- ),
- ),
- (
- "contact_outcome",
- models.CharField(
- choices=[
- ("SUCCESSFUL", "Successful Contact"),
- ("NO_ANSWER", "No Answer"),
- ("BUSY", "Line Busy"),
- ("VOICEMAIL", "Left Voicemail"),
- ("EMAIL_SENT", "Email Sent"),
- ("EMAIL_BOUNCED", "Email Bounced"),
- ("SMS_SENT", "SMS Sent"),
- ("SMS_FAILED", "SMS Failed"),
- ("WRONG_NUMBER", "Wrong Number"),
- ("DECLINED", "Patient Declined"),
- ],
- help_text="Outcome of contact attempt",
- max_length=20,
- ),
- ),
- (
- "appointment_offered",
- models.BooleanField(
- default=False,
- help_text="Appointment was offered during contact",
- ),
- ),
- (
- "offered_date",
- models.DateField(
- blank=True, help_text="Date of offered appointment", null=True
- ),
- ),
- (
- "offered_time",
- models.TimeField(
- blank=True, help_text="Time of offered appointment", null=True
- ),
- ),
- (
- "patient_response",
- models.CharField(
- blank=True,
- choices=[
- ("ACCEPTED", "Accepted Appointment"),
- ("DECLINED", "Declined Appointment"),
- ("REQUESTED_DIFFERENT", "Requested Different Time"),
- ("WILL_CALL_BACK", "Will Call Back"),
- ("NO_LONGER_NEEDED", "No Longer Needed"),
- ("INSURANCE_ISSUE", "Insurance Issue"),
- ("NO_RESPONSE", "No Response"),
- ],
- help_text="Patient response to contact",
- max_length=20,
- null=True,
- ),
- ),
- (
- "notes",
- models.TextField(
- blank=True, help_text="Notes from contact attempt", null=True
- ),
- ),
- (
- "next_contact_date",
- models.DateField(
- blank=True,
- help_text="Scheduled date for next contact attempt",
- null=True,
- ),
- ),
- (
- "contacted_by",
- models.ForeignKey(
- blank=True,
- help_text="Staff member who made contact",
- null=True,
- on_delete=django.db.models.deletion.SET_NULL,
- to=settings.AUTH_USER_MODEL,
- ),
- ),
- (
- "waiting_list_entry",
- models.ForeignKey(
- help_text="Associated waiting list entry",
- on_delete=django.db.models.deletion.CASCADE,
- related_name="contact_logs",
- to="appointments.waitinglist",
- ),
- ),
- ],
- options={
- "verbose_name": "Waiting List Contact Log",
- "verbose_name_plural": "Waiting List Contact Logs",
- "db_table": "appointments_waiting_list_contact_log",
- "ordering": ["-contact_date"],
- },
- ),
- migrations.AddIndex(
- model_name="waitinglist",
- index=models.Index(
- fields=["tenant", "status"], name="appointment_tenant__a558da_idx"
- ),
- ),
- migrations.AddIndex(
- model_name="waitinglist",
- index=models.Index(
- fields=["patient", "status"], name="appointment_patient_73f03d_idx"
- ),
- ),
- migrations.AddIndex(
- model_name="waitinglist",
- index=models.Index(
- fields=["department", "specialty", "status"],
- name="appointment_departm_78fd70_idx",
- ),
- ),
- migrations.AddIndex(
- model_name="waitinglist",
- index=models.Index(
- fields=["priority", "urgency_score"],
- name="appointment_priorit_30fb90_idx",
- ),
- ),
- migrations.AddIndex(
- model_name="waitinglist",
- index=models.Index(
- fields=["status", "created_at"], name="appointment_status_cfe551_idx"
- ),
- ),
- migrations.AddIndex(
- model_name="waitinglist",
- index=models.Index(
- fields=["provider", "status"], name="appointment_provide_dd6c2b_idx"
- ),
- ),
- migrations.AddIndex(
- model_name="waitinglistcontactlog",
- index=models.Index(
- fields=["waiting_list_entry", "contact_date"],
- name="appointment_waiting_50d8ac_idx",
- ),
- ),
- migrations.AddIndex(
- model_name="waitinglistcontactlog",
- index=models.Index(
- fields=["contact_outcome"], name="appointment_contact_ad9c45_idx"
- ),
- ),
- migrations.AddIndex(
- model_name="waitinglistcontactlog",
- index=models.Index(
- fields=["next_contact_date"], name="appointment_next_co_b29984_idx"
- ),
- ),
- ]
diff --git a/appointments/migrations/__pycache__/0001_initial.cpython-312.pyc b/appointments/migrations/__pycache__/0001_initial.cpython-312.pyc
index 86ea2ae4..c029329c 100644
Binary files a/appointments/migrations/__pycache__/0001_initial.cpython-312.pyc and b/appointments/migrations/__pycache__/0001_initial.cpython-312.pyc differ
diff --git a/appointments/migrations/__pycache__/0002_initial.cpython-312.pyc b/appointments/migrations/__pycache__/0002_initial.cpython-312.pyc
index f9fe7034..e8ddf75a 100644
Binary files a/appointments/migrations/__pycache__/0002_initial.cpython-312.pyc and b/appointments/migrations/__pycache__/0002_initial.cpython-312.pyc differ
diff --git a/appointments/migrations/__pycache__/0003_alter_waitinglist_acceptable_days.cpython-312.pyc b/appointments/migrations/__pycache__/0003_alter_waitinglist_acceptable_days.cpython-312.pyc
new file mode 100644
index 00000000..6ae7458f
Binary files /dev/null and b/appointments/migrations/__pycache__/0003_alter_waitinglist_acceptable_days.cpython-312.pyc differ
diff --git a/appointments/migrations/__pycache__/0003_waitinglist_waitinglistcontactlog_and_more.cpython-312.pyc b/appointments/migrations/__pycache__/0003_waitinglist_waitinglistcontactlog_and_more.cpython-312.pyc
deleted file mode 100644
index 12a8e50c..00000000
Binary files a/appointments/migrations/__pycache__/0003_waitinglist_waitinglistcontactlog_and_more.cpython-312.pyc and /dev/null differ
diff --git a/appointments/models.py b/appointments/models.py
index edb37303..06887680 100644
--- a/appointments/models.py
+++ b/appointments/models.py
@@ -1371,6 +1371,7 @@ class WaitingList(models.Model):
acceptable_days = models.JSONField(
default=list,
+ null=True,
blank=True,
help_text='Acceptable days of week (0=Monday, 6=Sunday)'
)
diff --git a/appointments/templates/appointments/.DS_Store b/appointments/templates/appointments/.DS_Store
index 4b8879d8..509758ba 100644
Binary files a/appointments/templates/appointments/.DS_Store and b/appointments/templates/appointments/.DS_Store differ
diff --git a/appointments/templates/appointments/appointment_search.html b/appointments/templates/appointments/appointment_search.html
index e593fd24..7f06a312 100644
--- a/appointments/templates/appointments/appointment_search.html
+++ b/appointments/templates/appointments/appointment_search.html
@@ -9,186 +9,186 @@
{% endblock %}
{% block content %}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
- {% if appointments %}
-
-
-
-
-
-
-
- | Date/Time |
- Patient |
- Provider |
- Department |
- Type |
- Status |
- Actions |
-
-
-
- {% for appointment in appointments %}
-
- |
- {{ appointment.appointment_date|date:"M d, Y" }}
- {{ appointment.appointment_time|time:"g:i A" }}
- |
-
- {{ appointment.patient.first_name }} {{ appointment.patient.last_name }}
- ID: {{ appointment.patient.patient_id }}
- |
- {{ appointment.provider.first_name }} {{ appointment.provider.last_name }} |
- {{ appointment.department.name }} |
- {{ appointment.appointment_type.name }} |
-
- {% if appointment.status == 'scheduled' %}
- Scheduled
- {% elif appointment.status == 'confirmed' %}
- Confirmed
- {% elif appointment.status == 'checked_in' %}
- Checked In
- {% elif appointment.status == 'in_progress' %}
- In Progress
- {% elif appointment.status == 'completed' %}
- Completed
- {% elif appointment.status == 'cancelled' %}
- Cancelled
- {% elif appointment.status == 'no_show' %}
- No Show
- {% endif %}
- |
-
-
-
-
-
-
-
-
- {% if appointment.status == 'scheduled' or appointment.status == 'confirmed' %}
-
-
-
- {% endif %}
-
- |
-
- {% endfor %}
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- {% elif request.GET %}
-
-
-
-
No appointments found
-
Try adjusting your search criteria
-
-
- {% endif %}
+
+ {% if appointments %}
+
+
+
+
+
+
+
+ | Date/Time |
+ Patient |
+ Provider |
+ Department |
+ Type |
+ Status |
+ Actions |
+
+
+
+ {% for appointment in appointments %}
+
+ |
+ {{ appointment.appointment_date|date:"M d, Y" }}
+ {{ appointment.appointment_time|time:"g:i A" }}
+ |
+
+ {{ appointment.patient.first_name }} {{ appointment.patient.last_name }}
+ ID: {{ appointment.patient.patient_id }}
+ |
+ {{ appointment.provider.first_name }} {{ appointment.provider.last_name }} |
+ {{ appointment.department.name }} |
+ {{ appointment.appointment_type.name }} |
+
+ {% if appointment.status == 'scheduled' %}
+ Scheduled
+ {% elif appointment.status == 'confirmed' %}
+ Confirmed
+ {% elif appointment.status == 'checked_in' %}
+ Checked In
+ {% elif appointment.status == 'in_progress' %}
+ In Progress
+ {% elif appointment.status == 'completed' %}
+ Completed
+ {% elif appointment.status == 'cancelled' %}
+ Cancelled
+ {% elif appointment.status == 'no_show' %}
+ No Show
+ {% endif %}
+ |
+
+
+
+
+
+
+
+
+ {% if appointment.status == 'scheduled' or appointment.status == 'confirmed' %}
+
+
+
+ {% endif %}
+
+ |
+
+ {% endfor %}
+
+
+
+
+
+ {% elif request.GET %}
+
+
+
+
No appointments found
+
Try adjusting your search criteria
+
+
+ {% endif %}
+
{% endblock %}
{% block js %}
diff --git a/appointments/templates/appointments/appointment_stats.html b/appointments/templates/appointments/appointment_stats.html
index 0367bb0f..0e1bc7df 100644
--- a/appointments/templates/appointments/appointment_stats.html
+++ b/appointments/templates/appointments/appointment_stats.html
@@ -8,232 +8,231 @@
{% endblock %}
{% block content %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ stats.total_appointments|default:0 }}
-
Total Appointments
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ stats.total_appointments|default:0 }}
+
Total Appointments
-
-
-
-
-
-
-
-
-
{{ stats.completed_appointments|default:0 }}
-
Completed
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ stats.cancelled_appointments|default:0 }}
-
Cancelled
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ stats.no_show_appointments|default:0 }}
-
No Shows
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
- | Status |
- Count |
- Percentage |
- Visual |
-
-
-
- {% for status in stats.by_status %}
-
- | {{ status.status|title }} |
- {{ status.count }} |
- {{ status.percentage|floatformat:1 }}% |
-
-
- |
-
- {% endfor %}
-
-
+
+
+
+
+
+
{{ stats.completed_appointments|default:0 }}
+
Completed
-
-
-
-
-
-
-
-
-
-
-
- | Department |
- Total |
- Completed |
- Cancelled |
- No Shows |
-
-
-
- {% for dept in stats.by_department %}
-
- | {{ dept.department_name }} |
- {{ dept.total }} |
- {{ dept.completed }} |
- {{ dept.cancelled }} |
- {{ dept.no_shows }} |
-
- {% endfor %}
-
-
+
+
-
-
-
-
-
-
-
-
Show Rate:
-
- {{ stats.show_rate|floatformat:1 }}%
-
+
+
+
+
+
+
+
{{ stats.cancelled_appointments|default:0 }}
+
Cancelled
-
-
Completion Rate:
-
- {{ stats.completion_rate|floatformat:1 }}%
-
-
-
-
Cancellation Rate:
-
- {{ stats.cancellation_rate|floatformat:1 }}%
-
-
-
-
Average Duration:
-
{{ stats.avg_duration|default:"N/A" }} min
-
-
-
Peak Hour:
-
{{ stats.peak_hour|default:"N/A" }}
+
+
-
-
-
-
-
- {% for provider in stats.top_providers %}
-
-
-
{{ provider.provider_name }}
-
{{ provider.department }}
-
-
-
{{ provider.appointment_count }}
-
appointments
-
+
+
+
+
+
+
+
+
{{ stats.no_show_appointments|default:0 }}
+
No Shows
+
+
+
- {% endfor %}
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Status |
+ Count |
+ Percentage |
+ Visual |
+
+
+
+ {% for status in stats.by_status %}
+
+ | {{ status.status|title }} |
+ {{ status.count }} |
+ {{ status.percentage|floatformat:1 }}% |
+
+
+ |
+
+ {% endfor %}
+
+
-
-
-
This Week:
-
{{ stats.this_week|default:0 }}
+
+
+
+
+
+
+
+
+
+
+
+ | Department |
+ Total |
+ Completed |
+ Cancelled |
+ No Shows |
+
+
+
+ {% for dept in stats.by_department %}
+
+ | {{ dept.department_name }} |
+ {{ dept.total }} |
+ {{ dept.completed }} |
+ {{ dept.cancelled }} |
+ {{ dept.no_shows }} |
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Show Rate:
+
+ {{ stats.show_rate|floatformat:1 }}%
-
-
Last Week:
-
{{ stats.last_week|default:0 }}
+
+
+
Completion Rate:
+
+ {{ stats.completion_rate|floatformat:1 }}%
-
-
This Month:
-
{{ stats.this_month|default:0 }}
+
+
+
Cancellation Rate:
+
+ {{ stats.cancellation_rate|floatformat:1 }}%
-
-
Last Month:
-
{{ stats.last_month|default:0 }}
+
+
+
Average Duration:
+
{{ stats.avg_duration|default:"N/A" }} min
+
+
+
Peak Hour:
+
{{ stats.peak_hour|default:"N/A" }}
+
+
+
+
+
+
+
+
+ {% for provider in stats.top_providers %}
+
+
+
{{ provider.provider_name }}
+
{{ provider.department }}
+
+
{{ provider.appointment_count }}
+
appointments
+
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
This Week:
+
{{ stats.this_week|default:0 }}
+
+
+
Last Week:
+
{{ stats.last_week|default:0 }}
+
+
+
This Month:
+
{{ stats.this_month|default:0 }}
+
+
+
Last Month:
+
{{ stats.last_month|default:0 }}
@@ -242,6 +241,7 @@
+
{% endblock %}
{% block js %}
diff --git a/appointments/templates/appointments/calendar.html b/appointments/templates/appointments/calendar.html
new file mode 100644
index 00000000..3ed08a5d
--- /dev/null
+++ b/appointments/templates/appointments/calendar.html
@@ -0,0 +1,182 @@
+{# templates/appointments/calendar.html #}
+{% extends "base.html" %}
+{% load static %}
+
+{% block title %}Appointments Calendar{% endblock %}
+
+{% block css %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+ SchedulingCalendar
+
+
View your calendar and manage schedules.
+
+
+
+
+
+
+
+
+
+
+
Click an event to see details.
+
+
+
+
+
+
+
+
+
+
+
+
+{# Optional: Bootstrap modal for full details #}
+
+{% endblock %}
+
+{% block js %}
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/appointments/templates/appointments/calendar_appointments.html b/appointments/templates/appointments/calendar_appointments.html
index a484a8ec..47ce17d8 100644
--- a/appointments/templates/appointments/calendar_appointments.html
+++ b/appointments/templates/appointments/calendar_appointments.html
@@ -7,152 +7,151 @@
{% endblock %}
{% block content %}
-
-
-
-
-
-
-
-
-
-
- New Appointment
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
Scheduled
+
+ {% for appointment in appointments %}
+
+
+
{{ appointment.appointment_time|time:"g:i A" }}
+
{{ appointment.patient.first_name }} {{ appointment.patient.last_name }}
+
{{ appointment.provider.first_name }} {{ appointment.provider.last_name }}
-
-
-
-
-
-
-
-
No Show
+
+ {% if appointment.status == 'SCHEDULED' %}
+ Scheduled
+ {% elif appointment.status == 'CONFIRMED' %}
+ Confirmed
+ {% elif appointment.status == 'CHECKED_IN' %}
+ Checked In
+ {% elif appointment.status == 'IN_PROGRESS' %}
+ In Progress
+ {% elif appointment.status == 'COMPLETED' %}
+ Completed
+ {% elif appointment.status == 'CANCELLED' %}
+ Cancelled
+ {% elif appointment.status == 'NO_SHOW' %}
+ No Show
+ {% endif %}
-
-
-
-
-
-
- {% for appointment in appointments %}
-
-
-
{{ appointment.appointment_time|time:"g:i A" }}
-
{{ appointment.patient.first_name }} {{ appointment.patient.last_name }}
-
{{ appointment.provider.first_name }} {{ appointment.provider.last_name }}
-
-
- {% if appointment.status == 'SCHEDULED' %}
- Scheduled
- {% elif appointment.status == 'CONFIRMED' %}
- Confirmed
- {% elif appointment.status == 'CHECKED_IN' %}
- Checked In
- {% elif appointment.status == 'IN_PROGRESS' %}
- In Progress
- {% elif appointment.status == 'COMPLETED' %}
- Completed
- {% elif appointment.status == 'CANCELLED' %}
- Cancelled
- {% elif appointment.status == 'NO_SHOW' %}
- No Show
- {% endif %}
-
-
- {% empty %}
-
No appointments today
- {% endfor %}
-
+ {% empty %}
+
No appointments today
+ {% endfor %}
@@ -161,6 +160,7 @@
+
diff --git a/appointments/templates/appointments/cancel_appointment.html b/appointments/templates/appointments/cancel_appointment.html
index b65bdfde..5f79f5c9 100644
--- a/appointments/templates/appointments/cancel_appointment.html
+++ b/appointments/templates/appointments/cancel_appointment.html
@@ -2,214 +2,267 @@
{% load static %}
{% block title %}Cancel Appointment{% endblock %}
-
+{% block css %}
+
+{% endblock %}
{% block content %}
-