update
This commit is contained in:
parent
97de5919f2
commit
1ae0e763be
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
@ -36,6 +36,13 @@ class Migration(migrations.Migration):
|
||||
('bio', models.TextField(blank=True)),
|
||||
('language', models.CharField(choices=[('en', 'English'), ('ar', 'Arabic')], default='en', max_length=5)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('is_provisional', models.BooleanField(default=False, help_text='User is in onboarding process')),
|
||||
('invitation_token', models.CharField(blank=True, help_text='Token for account activation', max_length=100, null=True, unique=True)),
|
||||
('invitation_expires_at', models.DateTimeField(blank=True, help_text='When the invitation token expires', null=True)),
|
||||
('acknowledgement_completed', models.BooleanField(default=False, help_text='User has completed acknowledgement wizard')),
|
||||
('acknowledgement_completed_at', models.DateTimeField(blank=True, help_text='When the acknowledgement was completed', null=True)),
|
||||
('current_wizard_step', models.IntegerField(default=0, help_text='Current step in onboarding wizard')),
|
||||
('wizard_completed_steps', models.JSONField(blank=True, default=list, help_text='List of completed wizard step IDs')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-date_joined'],
|
||||
@ -44,6 +51,49 @@ class Migration(migrations.Migration):
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AcknowledgementChecklistItem',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('role', models.CharField(blank=True, choices=[('px_admin', 'PX Admin'), ('hospital_admin', 'Hospital Admin'), ('department_manager', 'Department Manager'), ('px_coordinator', 'PX Coordinator'), ('physician', 'Physician'), ('nurse', 'Nurse'), ('staff', 'Staff'), ('viewer', 'Viewer')], help_text='Target role for this item', max_length=50, null=True)),
|
||||
('code', models.CharField(help_text='Unique code for this checklist item', max_length=100, unique=True)),
|
||||
('text_en', models.CharField(max_length=500)),
|
||||
('text_ar', models.CharField(blank=True, max_length=500)),
|
||||
('description_en', models.TextField(blank=True)),
|
||||
('description_ar', models.TextField(blank=True)),
|
||||
('is_required', models.BooleanField(default=True, help_text='Item must be acknowledged')),
|
||||
('order', models.IntegerField(default=0, help_text='Display order in checklist')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['role', 'order', 'code'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AcknowledgementContent',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('role', models.CharField(blank=True, choices=[('px_admin', 'PX Admin'), ('hospital_admin', 'Hospital Admin'), ('department_manager', 'Department Manager'), ('px_coordinator', 'PX Coordinator'), ('physician', 'Physician'), ('nurse', 'Nurse'), ('staff', 'Staff'), ('viewer', 'Viewer')], help_text='Target role for this content', max_length=50, null=True)),
|
||||
('code', models.CharField(help_text='Unique code for this content section', max_length=100, unique=True)),
|
||||
('title_en', models.CharField(max_length=200)),
|
||||
('title_ar', models.CharField(blank=True, max_length=200)),
|
||||
('description_en', models.TextField()),
|
||||
('description_ar', models.TextField(blank=True)),
|
||||
('content_en', models.TextField(blank=True)),
|
||||
('content_ar', models.TextField(blank=True)),
|
||||
('icon', models.CharField(blank=True, help_text="Icon class (e.g., 'fa-user', 'fa-shield')", max_length=50)),
|
||||
('color', models.CharField(blank=True, help_text="Hex color code (e.g., '#007bff')", max_length=7)),
|
||||
('order', models.IntegerField(default=0, help_text='Display order in wizard')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['role', 'order', 'code'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Role',
|
||||
fields=[
|
||||
@ -59,4 +109,37 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['-level', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserAcknowledgement',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('is_acknowledged', models.BooleanField(default=True)),
|
||||
('acknowledged_at', models.DateTimeField(auto_now_add=True)),
|
||||
('signature', models.TextField(blank=True, help_text='Digital signature data (base64 encoded)')),
|
||||
('signature_ip', models.GenericIPAddressField(blank=True, help_text='IP address when signed', null=True)),
|
||||
('signature_user_agent', models.TextField(blank=True, help_text='User agent when signed')),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text='Additional metadata')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-acknowledged_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserProvisionalLog',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('event_type', models.CharField(choices=[('created', 'User Created'), ('invitation_sent', 'Invitation Sent'), ('invitation_resent', 'Invitation Resent'), ('wizard_started', 'Wizard Started'), ('step_completed', 'Wizard Step Completed'), ('wizard_completed', 'Wizard Completed'), ('user_activated', 'User Activated'), ('invitation_expired', 'Invitation Expired')], db_index=True, max_length=50)),
|
||||
('description', models.TextField()),
|
||||
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
|
||||
('user_agent', models.TextField(blank=True)),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text='Additional event data')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@ -35,6 +36,19 @@ class Migration(migrations.Migration):
|
||||
name='user_permissions',
|
||||
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='acknowledgementcontent',
|
||||
index=models.Index(fields=['role', 'is_active', 'order'], name='accounts_ac_role_6fe1fd_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='acknowledgementcontent',
|
||||
index=models.Index(fields=['code'], name='accounts_ac_code_48fa92_idx'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='acknowledgementchecklistitem',
|
||||
name='content',
|
||||
field=models.ForeignKey(blank=True, help_text='Related content section', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checklist_items', to='accounts.acknowledgementcontent'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='role',
|
||||
name='group',
|
||||
@ -45,6 +59,21 @@ class Migration(migrations.Migration):
|
||||
name='permissions',
|
||||
field=models.ManyToManyField(blank=True, to='auth.permission'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='useracknowledgement',
|
||||
name='checklist_item',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_acknowledgements', to='accounts.acknowledgementchecklistitem'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='useracknowledgement',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='acknowledgements', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='userprovisionallog',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='provisional_logs', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='user',
|
||||
index=models.Index(fields=['email'], name='accounts_us_email_74c8d6_idx'),
|
||||
@ -57,4 +86,32 @@ class Migration(migrations.Migration):
|
||||
model_name='user',
|
||||
index=models.Index(fields=['is_active', '-date_joined'], name='accounts_us_is_acti_a32178_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='acknowledgementchecklistitem',
|
||||
index=models.Index(fields=['role', 'is_active', 'order'], name='accounts_ac_role_c556c1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='acknowledgementchecklistitem',
|
||||
index=models.Index(fields=['code'], name='accounts_ac_code_b745de_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='useracknowledgement',
|
||||
index=models.Index(fields=['user', '-acknowledged_at'], name='accounts_us_user_id_7ba948_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='useracknowledgement',
|
||||
index=models.Index(fields=['checklist_item', '-acknowledged_at'], name='accounts_us_checkli_870e26_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='useracknowledgement',
|
||||
unique_together={('user', 'checklist_item')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='userprovisionallog',
|
||||
index=models.Index(fields=['user', '-created_at'], name='accounts_us_user_id_c488d5_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='userprovisionallog',
|
||||
index=models.Index(fields=['event_type', '-created_at'], name='accounts_us_event_t_b7f691_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@ -1,160 +0,0 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-06 08:54
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='acknowledgement_completed',
|
||||
field=models.BooleanField(default=False, help_text='User has completed acknowledgement wizard'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='acknowledgement_completed_at',
|
||||
field=models.DateTimeField(blank=True, help_text='When the acknowledgement was completed', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='current_wizard_step',
|
||||
field=models.IntegerField(default=0, help_text='Current step in onboarding wizard'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='invitation_expires_at',
|
||||
field=models.DateTimeField(blank=True, help_text='When the invitation token expires', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='invitation_token',
|
||||
field=models.CharField(blank=True, help_text='Token for account activation', max_length=100, null=True, unique=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='is_provisional',
|
||||
field=models.BooleanField(default=False, help_text='User is in onboarding process'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='wizard_completed_steps',
|
||||
field=models.JSONField(blank=True, default=list, help_text='List of completed wizard step IDs'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AcknowledgementContent',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('role', models.CharField(blank=True, choices=[('px_admin', 'PX Admin'), ('hospital_admin', 'Hospital Admin'), ('department_manager', 'Department Manager'), ('px_coordinator', 'PX Coordinator'), ('physician', 'Physician'), ('nurse', 'Nurse'), ('staff', 'Staff'), ('viewer', 'Viewer')], help_text='Target role for this content', max_length=50, null=True)),
|
||||
('code', models.CharField(help_text='Unique code for this content section', max_length=100, unique=True)),
|
||||
('title_en', models.CharField(max_length=200)),
|
||||
('title_ar', models.CharField(blank=True, max_length=200)),
|
||||
('description_en', models.TextField()),
|
||||
('description_ar', models.TextField(blank=True)),
|
||||
('content_en', models.TextField(blank=True)),
|
||||
('content_ar', models.TextField(blank=True)),
|
||||
('icon', models.CharField(blank=True, help_text="Icon class (e.g., 'fa-user', 'fa-shield')", max_length=50)),
|
||||
('color', models.CharField(blank=True, help_text="Hex color code (e.g., '#007bff')", max_length=7)),
|
||||
('order', models.IntegerField(default=0, help_text='Display order in wizard')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['role', 'order', 'code'],
|
||||
'indexes': [models.Index(fields=['role', 'is_active', 'order'], name='accounts_ac_role_6fe1fd_idx'), models.Index(fields=['code'], name='accounts_ac_code_48fa92_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AcknowledgementChecklistItem',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('role', models.CharField(blank=True, choices=[('px_admin', 'PX Admin'), ('hospital_admin', 'Hospital Admin'), ('department_manager', 'Department Manager'), ('px_coordinator', 'PX Coordinator'), ('physician', 'Physician'), ('nurse', 'Nurse'), ('staff', 'Staff'), ('viewer', 'Viewer')], help_text='Target role for this item', max_length=50, null=True)),
|
||||
('code', models.CharField(help_text='Unique code for this checklist item', max_length=100, unique=True)),
|
||||
('text_en', models.CharField(max_length=500)),
|
||||
('text_ar', models.CharField(blank=True, max_length=500)),
|
||||
('description_en', models.TextField(blank=True)),
|
||||
('description_ar', models.TextField(blank=True)),
|
||||
('is_required', models.BooleanField(default=True, help_text='Item must be acknowledged')),
|
||||
('order', models.IntegerField(default=0, help_text='Display order in checklist')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('content', models.ForeignKey(blank=True, help_text='Related content section', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='checklist_items', to='accounts.acknowledgementcontent')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['role', 'order', 'code'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserAcknowledgement',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('is_acknowledged', models.BooleanField(default=True)),
|
||||
('acknowledged_at', models.DateTimeField(auto_now_add=True)),
|
||||
('signature', models.TextField(blank=True, help_text='Digital signature data (base64 encoded)')),
|
||||
('signature_ip', models.GenericIPAddressField(blank=True, help_text='IP address when signed', null=True)),
|
||||
('signature_user_agent', models.TextField(blank=True, help_text='User agent when signed')),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text='Additional metadata')),
|
||||
('checklist_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_acknowledgements', to='accounts.acknowledgementchecklistitem')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='acknowledgements', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-acknowledged_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserProvisionalLog',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('event_type', models.CharField(choices=[('created', 'User Created'), ('invitation_sent', 'Invitation Sent'), ('invitation_resent', 'Invitation Resent'), ('wizard_started', 'Wizard Started'), ('step_completed', 'Wizard Step Completed'), ('wizard_completed', 'Wizard Completed'), ('user_activated', 'User Activated'), ('invitation_expired', 'Invitation Expired')], db_index=True, max_length=50)),
|
||||
('description', models.TextField()),
|
||||
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
|
||||
('user_agent', models.TextField(blank=True)),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text='Additional event data')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='provisional_logs', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='acknowledgementchecklistitem',
|
||||
index=models.Index(fields=['role', 'is_active', 'order'], name='accounts_ac_role_c556c1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='acknowledgementchecklistitem',
|
||||
index=models.Index(fields=['code'], name='accounts_ac_code_b745de_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='useracknowledgement',
|
||||
index=models.Index(fields=['user', '-acknowledged_at'], name='accounts_us_user_id_7ba948_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='useracknowledgement',
|
||||
index=models.Index(fields=['checklist_item', '-acknowledged_at'], name='accounts_us_checkli_870e26_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='useracknowledgement',
|
||||
unique_together={('user', 'checklist_item')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='userprovisionallog',
|
||||
index=models.Index(fields=['user', '-created_at'], name='accounts_us_user_id_c488d5_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='userprovisionallog',
|
||||
index=models.Index(fields=['event_type', '-created_at'], name='accounts_us_event_t_b7f691_idx'),
|
||||
),
|
||||
]
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2025-12-14 11:19
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -550,33 +550,33 @@ class UnifiedAnalyticsService:
|
||||
queryset = PhysicianMonthlyRating.objects.filter(
|
||||
year=now.year,
|
||||
month=now.month
|
||||
).select_related('physician', 'physician__hospital', 'physician__department')
|
||||
).select_related('staff', 'staff__hospital', 'staff__department')
|
||||
|
||||
# Apply RBAC filters
|
||||
if not user.is_px_admin() and user.hospital:
|
||||
queryset = queryset.filter(physician__hospital=user.hospital)
|
||||
queryset = queryset.filter(staff__hospital=user.hospital)
|
||||
|
||||
if hospital_id:
|
||||
queryset = queryset.filter(physician__hospital_id=hospital_id)
|
||||
queryset = queryset.filter(staff__hospital_id=hospital_id)
|
||||
|
||||
if department_id:
|
||||
queryset = queryset.filter(physician__department_id=department_id)
|
||||
queryset = queryset.filter(staff__department_id=department_id)
|
||||
|
||||
queryset = queryset.order_by('-average_rating')[:limit]
|
||||
|
||||
return {
|
||||
'type': 'bar',
|
||||
'labels': [r.physician.get_full_name() for r in queryset],
|
||||
'labels': [f"{r.staff.first_name} {r.staff.last_name}" for r in queryset],
|
||||
'series': [{
|
||||
'name': 'Rating',
|
||||
'data': [float(round(r.average_rating, 2)) for r in queryset]
|
||||
}],
|
||||
'metadata': [
|
||||
{
|
||||
'name': r.physician.get_full_name(),
|
||||
'physician_id': str(r.physician.id),
|
||||
'specialization': r.physician.specialization,
|
||||
'department': r.physician.department.name if r.physician.department else None,
|
||||
'name': f"{r.staff.first_name} {r.staff.last_name}",
|
||||
'physician_id': str(r.staff.id),
|
||||
'specialization': r.staff.specialization,
|
||||
'department': r.staff.department.name if r.staff.department else None,
|
||||
'rating': float(round(r.average_rating, 2)),
|
||||
'surveys': int(r.total_surveys) if r.total_surveys is not None else 0,
|
||||
'positive': int(r.positive_count) if r.positive_count is not None else 0,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-01 11:27
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -109,8 +109,8 @@ class Command(BaseCommand):
|
||||
created_count = 0
|
||||
for cat_data in categories:
|
||||
category, created = ComplaintCategory.objects.get_or_create(
|
||||
hospital=None, # System-wide
|
||||
code=cat_data['code'],
|
||||
parent__isnull=True, # System-wide categories have no parent
|
||||
defaults={
|
||||
'name_en': cat_data['name_en'],
|
||||
'name_ar': cat_data['name_ar'],
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
@ -48,7 +48,7 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Complaint Categories',
|
||||
'ordering': ['hospital', 'order', 'name_en'],
|
||||
'ordering': ['order', 'name_en'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -140,6 +140,38 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InquiryAttachment',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('file', models.FileField(upload_to='inquiries/%Y/%m/%d/')),
|
||||
('filename', models.CharField(max_length=500)),
|
||||
('file_type', models.CharField(blank=True, max_length=100)),
|
||||
('file_size', models.IntegerField(help_text='File size in bytes')),
|
||||
('description', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InquiryUpdate',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('update_type', models.CharField(choices=[('status_change', 'Status Change'), ('assignment', 'Assignment'), ('note', 'Note'), ('response', 'Response'), ('communication', 'Communication')], db_index=True, max_length=50)),
|
||||
('message', models.TextField()),
|
||||
('old_status', models.CharField(blank=True, max_length=20)),
|
||||
('new_status', models.CharField(blank=True, max_length=20)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Complaint',
|
||||
fields=[
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
@ -44,8 +44,8 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='complaintcategory',
|
||||
name='hospital',
|
||||
field=models.ForeignKey(blank=True, help_text='Leave blank for system-wide categories', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='complaint_categories', to='organizations.hospital'),
|
||||
name='hospitals',
|
||||
field=models.ManyToManyField(blank=True, help_text='Empty list = system-wide category. Add hospitals to share category.', related_name='complaint_categories', to='organizations.hospital'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='complaintcategory',
|
||||
@ -112,9 +112,25 @@ class Migration(migrations.Migration):
|
||||
name='responded_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='responded_inquiries', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='complaintcategory',
|
||||
index=models.Index(fields=['hospital', 'is_active'], name='complaints__hospita_a31674_idx'),
|
||||
migrations.AddField(
|
||||
model_name='inquiryattachment',
|
||||
name='inquiry',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='complaints.inquiry'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inquiryattachment',
|
||||
name='uploaded_by',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inquiry_attachments', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inquiryupdate',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inquiry_updates', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inquiryupdate',
|
||||
name='inquiry',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='updates', to='complaints.inquiry'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='complaintcategory',
|
||||
@ -168,4 +184,8 @@ class Migration(migrations.Migration):
|
||||
model_name='inquiry',
|
||||
index=models.Index(fields=['hospital', 'status'], name='complaints__hospita_b1573b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='inquiryupdate',
|
||||
index=models.Index(fields=['inquiry', '-created_at'], name='complaints__inquiry_551c37_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 13:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('complaints', '0002_initial'),
|
||||
('organizations', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='complaintcategory',
|
||||
options={'ordering': ['order', 'name_en'], 'verbose_name_plural': 'Complaint Categories'},
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='complaintcategory',
|
||||
name='complaints__hospita_a31674_idx',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='complaintcategory',
|
||||
name='hospital',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='complaintcategory',
|
||||
name='hospitals',
|
||||
field=models.ManyToManyField(blank=True, help_text='Empty list = system-wide category. Add hospitals to share category.', related_name='complaint_categories', to='organizations.hospital'),
|
||||
),
|
||||
]
|
||||
@ -1,54 +0,0 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 15:06
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('complaints', '0003_alter_complaintcategory_options_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InquiryAttachment',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('file', models.FileField(upload_to='inquiries/%Y/%m/%d/')),
|
||||
('filename', models.CharField(max_length=500)),
|
||||
('file_type', models.CharField(blank=True, max_length=100)),
|
||||
('file_size', models.IntegerField(help_text='File size in bytes')),
|
||||
('description', models.TextField(blank=True)),
|
||||
('inquiry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='complaints.inquiry')),
|
||||
('uploaded_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inquiry_attachments', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InquiryUpdate',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('update_type', models.CharField(choices=[('status_change', 'Status Change'), ('assignment', 'Assignment'), ('note', 'Note'), ('response', 'Response'), ('communication', 'Communication')], db_index=True, max_length=50)),
|
||||
('message', models.TextField()),
|
||||
('old_status', models.CharField(blank=True, max_length=20)),
|
||||
('new_status', models.CharField(blank=True, max_length=20)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inquiry_updates', to=settings.AUTH_USER_MODEL)),
|
||||
('inquiry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='updates', to='complaints.inquiry')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['inquiry', '-created_at'], name='complaints__inquiry_551c37_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -35,7 +35,10 @@ class AIService:
|
||||
"""
|
||||
|
||||
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
OPENROUTER_API_KEY = "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"
|
||||
# OPENROUTER_API_KEY = "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"
|
||||
OPENROUTER_API_KEY = "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"
|
||||
|
||||
|
||||
# Default configuration
|
||||
DEFAULT_MODEL = "openrouter/z-ai/glm-4.7"
|
||||
# DEFAULT_MODEL = "openrouter/xiaomi/mimo-v2-flash:free"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-04 07:26
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import apps.observations.models
|
||||
import django.db.models.deletion
|
||||
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('organizations', '0002_hospital_metadata'),
|
||||
('organizations', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
@ -54,6 +54,7 @@ class Migration(migrations.Migration):
|
||||
('reporter_phone', models.CharField(blank=True, help_text='Optional phone number for follow-up', max_length=20)),
|
||||
('reporter_email', models.EmailField(blank=True, help_text='Optional email for follow-up', max_length=254)),
|
||||
('status', models.CharField(choices=[('new', 'New'), ('triaged', 'Triaged'), ('assigned', 'Assigned'), ('in_progress', 'In Progress'), ('resolved', 'Resolved'), ('closed', 'Closed'), ('rejected', 'Rejected'), ('duplicate', 'Duplicate')], db_index=True, default='new', max_length=20)),
|
||||
('source', models.CharField(choices=[('staff_portal', 'Staff Portal'), ('web_form', 'Web Form'), ('mobile_app', 'Mobile App'), ('email', 'Email'), ('call_center', 'Call Center'), ('other', 'Other')], default='staff_portal', help_text='How the observation was submitted', max_length=50)),
|
||||
('triaged_at', models.DateTimeField(blank=True, null=True)),
|
||||
('resolved_at', models.DateTimeField(blank=True, null=True)),
|
||||
('resolution_notes', models.TextField(blank=True)),
|
||||
@ -65,7 +66,9 @@ class Migration(migrations.Migration):
|
||||
('assigned_department', models.ForeignKey(blank=True, help_text='Department responsible for handling this observation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_observations', to='organizations.department')),
|
||||
('assigned_to', models.ForeignKey(blank=True, help_text='User assigned to handle this observation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_observations', to=settings.AUTH_USER_MODEL)),
|
||||
('closed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='closed_observations', to=settings.AUTH_USER_MODEL)),
|
||||
('hospital', models.ForeignKey(help_text='Hospital where observation was made', on_delete=django.db.models.deletion.CASCADE, related_name='observations', to='organizations.hospital')),
|
||||
('resolved_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='resolved_observations', to=settings.AUTH_USER_MODEL)),
|
||||
('staff', models.ForeignKey(blank=True, help_text='Staff member mentioned in observation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observations', to='organizations.staff')),
|
||||
('triaged_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='triaged_observations', to=settings.AUTH_USER_MODEL)),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observations', to='observations.observationcategory')),
|
||||
],
|
||||
@ -124,6 +127,10 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='observation',
|
||||
index=models.Index(fields=['hospital', 'status', '-created_at'], name='observation_hospita_dcd21a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='observation',
|
||||
index=models.Index(fields=['status', '-created_at'], name='observation_status_2b5566_idx'),
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
# Generated migration to add missing fields to Observation model
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('organizations', '0001_initial'), # Need hospital and department models
|
||||
('observations', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Add hospital field (required for tenant isolation)
|
||||
# Initially nullable, will be made required in next migration
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='hospital',
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='observations',
|
||||
to='organizations.hospital'
|
||||
),
|
||||
),
|
||||
|
||||
# Add staff field (optional, for AI-matching like complaints)
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='staff',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='observations',
|
||||
to='organizations.staff'
|
||||
),
|
||||
),
|
||||
|
||||
# Add source field (to track how observation was submitted)
|
||||
migrations.AddField(
|
||||
model_name='observation',
|
||||
name='source',
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
('staff_portal', 'Staff Portal'),
|
||||
('web_form', 'Web Form'),
|
||||
('mobile_app', 'Mobile App'),
|
||||
('email', 'Email'),
|
||||
('call_center', 'Call Center'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
default='staff_portal',
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
|
||||
# Add indexes for hospital filtering
|
||||
migrations.AddIndex(
|
||||
model_name='observation',
|
||||
index=models.Index(fields=['hospital', 'status', '-created_at'], name='obs_hospital_status_idx'),
|
||||
),
|
||||
]
|
||||
@ -1,36 +0,0 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-07 11:02
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('observations', '0002_add_missing_fields'),
|
||||
('organizations', '0005_alter_staff_department'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameIndex(
|
||||
model_name='observation',
|
||||
new_name='observation_hospita_dcd21a_idx',
|
||||
old_name='obs_hospital_status_idx',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='observation',
|
||||
name='hospital',
|
||||
field=models.ForeignKey(default=1, help_text='Hospital where observation was made', on_delete=django.db.models.deletion.CASCADE, related_name='observations', to='organizations.hospital'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='observation',
|
||||
name='source',
|
||||
field=models.CharField(choices=[('staff_portal', 'Staff Portal'), ('web_form', 'Web Form'), ('mobile_app', 'Mobile App'), ('email', 'Email'), ('call_center', 'Call Center'), ('other', 'Other')], default='staff_portal', help_text='How the observation was submitted', max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='observation',
|
||||
name='staff',
|
||||
field=models.ForeignKey(blank=True, help_text='Staff member mentioned in observation', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observations', to='organizations.staff'),
|
||||
),
|
||||
]
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
@ -31,7 +31,7 @@ class Migration(migrations.Migration):
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive'), ('pending', 'Pending'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], db_index=True, default='active', max_length=20)),
|
||||
('license_number', models.CharField(blank=True, max_length=100)),
|
||||
('capacity', models.IntegerField(blank=True, help_text='Bed capacity', null=True)),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text='Hospital configuration settings')),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Hospitals',
|
||||
@ -130,7 +130,7 @@ class Migration(migrations.Migration):
|
||||
('specialization', models.CharField(blank=True, max_length=200)),
|
||||
('employee_id', models.CharField(db_index=True, max_length=50, unique=True)),
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive'), ('pending', 'Pending'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='active', max_length=20)),
|
||||
('department', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organizations.department')),
|
||||
('department', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='staff', to='organizations.department')),
|
||||
('hospital', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='organizations.hospital')),
|
||||
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='staff_profile', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-01 12:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('organizations', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='hospital',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, default=dict),
|
||||
),
|
||||
]
|
||||
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-06 11:55
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('organizations', '0002_hospital_metadata'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='patient',
|
||||
name='department',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organizations.department'),
|
||||
),
|
||||
]
|
||||
@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-06 11:56
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('organizations', '0003_patient_department'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='patient',
|
||||
name='department',
|
||||
),
|
||||
]
|
||||
@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-07 08:54
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('organizations', '0004_remove_patient_department'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='staff',
|
||||
name='department',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='staff', to='organizations.department'),
|
||||
),
|
||||
]
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-07 16:18
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import apps.references.models
|
||||
import django.db.models.deletion
|
||||
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('organizations', '0005_alter_staff_department'),
|
||||
('organizations', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-07 20:27
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
@ -12,7 +12,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('organizations', '0005_alter_staff_department'),
|
||||
('organizations', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.14 on 2026-01-05 10:43
|
||||
# Generated by Django 5.0.14 on 2026-01-08 06:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -197,7 +197,7 @@ def generate_saudi_phone():
|
||||
|
||||
def generate_mrn():
|
||||
"""Generate Medical Record Number"""
|
||||
return f"MRN{random.randint(100000, 999999)}"
|
||||
return f"{random.randint(100000, 999999)}"
|
||||
|
||||
|
||||
def generate_national_id():
|
||||
@ -368,7 +368,7 @@ def create_complaint_categories(hospitals):
|
||||
"""Create complaint categories"""
|
||||
print("Creating complaint categories...")
|
||||
|
||||
# System-wide categories (hospital=None)
|
||||
# System-wide categories (no hospitals in ManyToMany)
|
||||
system_categories = [
|
||||
{'code': 'CLINICAL', 'name_en': 'Clinical Care', 'name_ar': 'الرعاية السريرية', 'order': 1},
|
||||
{'code': 'STAFF', 'name_en': 'Staff Behavior', 'name_ar': 'سلوك الموظفين', 'order': 2},
|
||||
@ -380,11 +380,11 @@ def create_complaint_categories(hospitals):
|
||||
|
||||
categories = []
|
||||
|
||||
# Create system-wide categories
|
||||
# Create system-wide categories (parent__isnull=True and no hospitals)
|
||||
for cat_data in system_categories:
|
||||
category, created = ComplaintCategory.objects.get_or_create(
|
||||
code=cat_data['code'],
|
||||
hospital=None,
|
||||
parent__isnull=True,
|
||||
defaults={
|
||||
'name_en': cat_data['name_en'],
|
||||
'name_ar': cat_data['name_ar'],
|
||||
@ -392,9 +392,9 @@ def create_complaint_categories(hospitals):
|
||||
'is_active': True,
|
||||
}
|
||||
)
|
||||
categories.append(category)
|
||||
if created:
|
||||
print(f" Created system-wide category: {category.name_en}")
|
||||
categories.append(category)
|
||||
|
||||
# Create hospital-specific categories for each hospital
|
||||
hospital_specific_categories = [
|
||||
@ -407,7 +407,6 @@ def create_complaint_categories(hospitals):
|
||||
for cat_data in hospital_specific_categories:
|
||||
category, created = ComplaintCategory.objects.get_or_create(
|
||||
code=f"{cat_data['code']}_{hospital.code}",
|
||||
hospital=hospital,
|
||||
defaults={
|
||||
'name_en': cat_data['name_en'],
|
||||
'name_ar': cat_data['name_ar'],
|
||||
@ -415,9 +414,14 @@ def create_complaint_categories(hospitals):
|
||||
'is_active': True,
|
||||
}
|
||||
)
|
||||
categories.append(category)
|
||||
if created:
|
||||
category.hospitals.add(hospital)
|
||||
print(f" Created hospital category for {hospital.name}: {category.name_en}")
|
||||
else:
|
||||
# Ensure hospital is in the hospitals ManyToMany
|
||||
if hospital not in category.hospitals.all():
|
||||
category.hospitals.add(hospital)
|
||||
categories.append(category)
|
||||
|
||||
print(f" Created {len(categories)} complaint categories")
|
||||
return categories
|
||||
@ -438,7 +442,7 @@ def create_complaints(patients, hospitals, staff, users):
|
||||
|
||||
# Generate complaints over 2 years (730 days)
|
||||
# Average 3-5 complaints per day = ~1200-1800 total
|
||||
for day_offset in range(730):
|
||||
for day_offset in range(30):
|
||||
# Random number of complaints per day (0-8, weighted towards 2-4)
|
||||
num_complaints = random.choices([0, 1, 2, 3, 4, 5, 6, 7, 8], weights=[5, 10, 20, 25, 20, 10, 5, 3, 2])[0]
|
||||
|
||||
@ -456,8 +460,8 @@ def create_complaints(patients, hospitals, staff, users):
|
||||
status = random.choices(['open', 'in_progress', 'resolved', 'closed'], weights=[2, 5, 30, 63])[0]
|
||||
|
||||
# Select appropriate category (system-wide or hospital-specific)
|
||||
hospital_categories = [c for c in categories if c.hospital == hospital]
|
||||
system_categories_list = [c for c in categories if c.hospital is None]
|
||||
hospital_categories = [c for c in categories if hospital in c.hospitals.all()]
|
||||
system_categories_list = [c for c in categories if c.hospitals.count() == 0]
|
||||
|
||||
# Prefer hospital-specific categories if available, otherwise use system-wide
|
||||
available_categories = hospital_categories if hospital_categories else system_categories_list
|
||||
@ -469,7 +473,7 @@ def create_complaints(patients, hospitals, staff, users):
|
||||
staff=random.choice(staff) if random.random() > 0.5 else None,
|
||||
title=random.choice(COMPLAINT_TITLES),
|
||||
description=f"Detailed description of the complaint. Patient experienced issues during their visit.",
|
||||
category=random.choice(available_categories),
|
||||
category=random.choice(available_categories) if available_categories else None,
|
||||
priority=random.choice(['low', 'medium', 'high']),
|
||||
severity=random.choice(['low', 'medium', 'high', 'critical']),
|
||||
source=random.choice(['patient', 'family', 'survey', 'call_center', 'moh', 'other']),
|
||||
@ -519,7 +523,7 @@ def create_inquiries(patients, hospitals, users):
|
||||
|
||||
# Generate inquiries over 2 years (730 days)
|
||||
# Average 1-2 inquiries per day = ~500-700 total
|
||||
for day_offset in range(730):
|
||||
for day_offset in range(30):
|
||||
num_inquiries = random.choices([0, 1, 2, 3], weights=[30, 40, 25, 5])[0]
|
||||
|
||||
for _ in range(num_inquiries):
|
||||
@ -838,6 +842,7 @@ def create_survey_instances(survey_templates, patients, staff):
|
||||
|
||||
instance = SurveyInstance.objects.create(
|
||||
survey_template=template,
|
||||
hospital=template.hospital,
|
||||
patient=patient,
|
||||
delivery_channel=random.choice(['sms', 'whatsapp', 'email']),
|
||||
recipient_phone=patient.phone,
|
||||
@ -961,7 +966,7 @@ def create_staff_monthly_ratings(staff):
|
||||
neutral_count = total_surveys - positive_count - negative_count
|
||||
|
||||
rating, created = PhysicianMonthlyRating.objects.get_or_create(
|
||||
physician=staff_member,
|
||||
staff=staff_member,
|
||||
year=year,
|
||||
month=month,
|
||||
defaults={
|
||||
@ -979,10 +984,10 @@ def create_staff_monthly_ratings(staff):
|
||||
)
|
||||
ratings.append(rating)
|
||||
|
||||
print(f" Created {len(ratings)} physician monthly ratings")
|
||||
print(f" Created {len(ratings)} staff monthly ratings")
|
||||
|
||||
# Update rankings for each month
|
||||
print(" Updating physician rankings...")
|
||||
print(" Updating staff rankings...")
|
||||
from apps.physicians.models import PhysicianMonthlyRating
|
||||
from apps.organizations.models import Hospital, Department
|
||||
|
||||
@ -994,7 +999,7 @@ def create_staff_monthly_ratings(staff):
|
||||
hospitals = Hospital.objects.filter(status='active')
|
||||
for hospital in hospitals:
|
||||
hospital_ratings = PhysicianMonthlyRating.objects.filter(
|
||||
physician__hospital=hospital,
|
||||
staff__hospital=hospital,
|
||||
year=year,
|
||||
month=month
|
||||
).order_by('-average_rating')
|
||||
@ -1007,7 +1012,7 @@ def create_staff_monthly_ratings(staff):
|
||||
departments = Department.objects.filter(status='active')
|
||||
for department in departments:
|
||||
dept_ratings = PhysicianMonthlyRating.objects.filter(
|
||||
physician__department=department,
|
||||
staff__department=department,
|
||||
year=year,
|
||||
month=month
|
||||
).order_by('-average_rating')
|
||||
@ -1020,13 +1025,13 @@ def create_staff_monthly_ratings(staff):
|
||||
return ratings
|
||||
|
||||
|
||||
def create_appreciations(users, physicians, hospitals, departments, categories):
|
||||
def create_appreciations(users, staff, hospitals, departments, categories):
|
||||
"""Create appreciations with 2 years of historical data"""
|
||||
print("Creating appreciations (2 years of data)...")
|
||||
|
||||
# Get ContentType for User and Physician
|
||||
# Get ContentType for User and Staff
|
||||
user_ct = ContentType.objects.get_for_model(User)
|
||||
physician_ct = ContentType.objects.get_for_model(Physician)
|
||||
staff_ct = ContentType.objects.get_for_model(Staff)
|
||||
|
||||
# Message templates for generating realistic appreciations
|
||||
message_templates_en = [
|
||||
@ -1101,12 +1106,12 @@ def create_appreciations(users, physicians, hospitals, departments, categories):
|
||||
# Select sender (users only - users send appreciations)
|
||||
sender = random.choice(users)
|
||||
|
||||
# Select recipient (70% physician, 30% user)
|
||||
is_physician_recipient = random.random() < 0.7
|
||||
# Select recipient (70% staff, 30% user)
|
||||
is_staff_recipient = random.random() < 0.7
|
||||
|
||||
if is_physician_recipient:
|
||||
recipient = random.choice(physicians)
|
||||
recipient_ct = physician_ct
|
||||
if is_staff_recipient:
|
||||
recipient = random.choice(staff)
|
||||
recipient_ct = staff_ct
|
||||
else:
|
||||
# User recipients (excluding sender)
|
||||
potential_recipients = [u for u in users if u.id != sender.id]
|
||||
@ -1120,7 +1125,7 @@ def create_appreciations(users, physicians, hospitals, departments, categories):
|
||||
continue
|
||||
|
||||
# Determine hospital context
|
||||
if is_physician_recipient:
|
||||
if is_staff_recipient:
|
||||
hospital = recipient.hospital
|
||||
else:
|
||||
hospital = recipient.hospital if recipient.hospital else sender.hospital
|
||||
@ -1130,7 +1135,7 @@ def create_appreciations(users, physicians, hospitals, departments, categories):
|
||||
|
||||
# Determine department context
|
||||
department = None
|
||||
if is_physician_recipient and recipient.department:
|
||||
if is_staff_recipient and recipient.department:
|
||||
department = recipient.department
|
||||
elif random.random() < 0.3:
|
||||
# Some appreciations have department context
|
||||
@ -1233,14 +1238,14 @@ def create_appreciations(users, physicians, hospitals, departments, categories):
|
||||
return appreciations
|
||||
|
||||
|
||||
def award_badges(badges, users, physicians, categories):
|
||||
"""Award badges to users and physicians based on appreciations"""
|
||||
def award_badges(badges, users, staff, categories):
|
||||
"""Award badges to users and staff based on appreciations"""
|
||||
print("Awarding badges...")
|
||||
user_badges = []
|
||||
|
||||
# Get ContentType for User and Physician
|
||||
# Get ContentType for User and Staff
|
||||
user_ct = ContentType.objects.get_for_model(User)
|
||||
physician_ct = ContentType.objects.get_for_model(Physician)
|
||||
staff_ct = ContentType.objects.get_for_model(Staff)
|
||||
|
||||
# Badge criteria mapping (using codes from seed command)
|
||||
badge_criteria = {
|
||||
@ -1292,15 +1297,15 @@ def award_badges(badges, users, physicians, categories):
|
||||
appreciation_count=received_count,
|
||||
))
|
||||
|
||||
# Award badges to physicians (60% get badges)
|
||||
for physician in physicians:
|
||||
if random.random() > 0.6: # 60% of physicians get badges
|
||||
# Award badges to staff (60% get badges)
|
||||
for staff_member in staff:
|
||||
if random.random() > 0.6: # 60% of staff get badges
|
||||
continue
|
||||
|
||||
# Count appreciations received by this physician
|
||||
# Count appreciations received by this staff member
|
||||
received_count = Appreciation.objects.filter(
|
||||
recipient_content_type=physician_ct,
|
||||
recipient_object_id=physician.id,
|
||||
recipient_content_type=staff_ct,
|
||||
recipient_object_id=staff_member.id,
|
||||
status=AppreciationStatus.ACKNOWLEDGED
|
||||
).count()
|
||||
|
||||
@ -1312,17 +1317,17 @@ def award_badges(badges, users, physicians, categories):
|
||||
if received_count >= criteria['min_count']:
|
||||
# Check if already has this badge
|
||||
existing = UserBadge.objects.filter(
|
||||
recipient_content_type=physician_ct,
|
||||
recipient_object_id=physician.id,
|
||||
recipient_content_type=staff_ct,
|
||||
recipient_object_id=staff_member.id,
|
||||
badge=badge_map[badge_code]
|
||||
).first()
|
||||
|
||||
if not existing:
|
||||
# Higher chance for physicians
|
||||
# Higher chance for staff
|
||||
if random.random() < 0.7:
|
||||
user_badges.append(UserBadge(
|
||||
recipient_content_type=physician_ct,
|
||||
recipient_object_id=physician.id,
|
||||
recipient_content_type=staff_ct,
|
||||
recipient_object_id=staff_member.id,
|
||||
badge=badge_map[badge_code],
|
||||
appreciation_count=received_count,
|
||||
))
|
||||
@ -1330,19 +1335,19 @@ def award_badges(badges, users, physicians, categories):
|
||||
# Bulk create user badges
|
||||
UserBadge.objects.bulk_create(user_badges)
|
||||
|
||||
print(f" Awarded {len(user_badges)} badges to users and physicians")
|
||||
print(f" Awarded {len(user_badges)} badges to users and staff")
|
||||
return user_badges
|
||||
|
||||
|
||||
def generate_appreciation_stats(users, physicians, hospitals):
|
||||
"""Generate appreciation statistics for users and physicians"""
|
||||
def generate_appreciation_stats(users, staff, hospitals):
|
||||
"""Generate appreciation statistics for users and staff"""
|
||||
print("Generating appreciation statistics...")
|
||||
stats = []
|
||||
now = timezone.now()
|
||||
|
||||
# Get ContentType for User and Physician
|
||||
# Get ContentType for User and Staff
|
||||
user_ct = ContentType.objects.get_for_model(User)
|
||||
physician_ct = ContentType.objects.get_for_model(Physician)
|
||||
staff_ct = ContentType.objects.get_for_model(Staff)
|
||||
|
||||
# Get current year and month
|
||||
year = now.year
|
||||
@ -1358,41 +1363,46 @@ def generate_appreciation_stats(users, physicians, hospitals):
|
||||
sent_count = random.randint(0, 50)
|
||||
acknowledged_count = int(received_count * random.uniform(0.6, 1.0))
|
||||
|
||||
stats.append(AppreciationStats(
|
||||
stat, created = AppreciationStats.objects.get_or_create(
|
||||
recipient_content_type=user_ct,
|
||||
recipient_object_id=user.id,
|
||||
year=year,
|
||||
month=month,
|
||||
hospital=user.hospital if user.hospital else random.choice(hospitals),
|
||||
received_count=received_count,
|
||||
sent_count=sent_count,
|
||||
acknowledged_count=acknowledged_count,
|
||||
hospital_rank=random.randint(1, 20) if received_count > 0 else None,
|
||||
))
|
||||
defaults={
|
||||
'hospital': user.hospital if user.hospital else random.choice(hospitals),
|
||||
'received_count': received_count,
|
||||
'sent_count': sent_count,
|
||||
'acknowledged_count': acknowledged_count,
|
||||
'hospital_rank': random.randint(1, 20) if received_count > 0 else None,
|
||||
}
|
||||
)
|
||||
if created:
|
||||
stats.append(stat)
|
||||
|
||||
# Generate stats for physicians (90% have stats)
|
||||
for physician in physicians:
|
||||
# Generate stats for staff (90% have stats)
|
||||
for staff_member in staff:
|
||||
if random.random() > 0.1: # 90% have stats
|
||||
# Physicians typically receive more appreciations
|
||||
# Staff typically receive more appreciations
|
||||
received_count = random.randint(5, 50)
|
||||
sent_count = random.randint(0, 20) # Physicians send less
|
||||
sent_count = random.randint(0, 20) # Staff send less
|
||||
acknowledged_count = int(received_count * random.uniform(0.7, 1.0))
|
||||
|
||||
stats.append(AppreciationStats(
|
||||
recipient_content_type=physician_ct,
|
||||
recipient_object_id=physician.id,
|
||||
stat, created = AppreciationStats.objects.get_or_create(
|
||||
recipient_content_type=staff_ct,
|
||||
recipient_object_id=staff_member.id,
|
||||
year=year,
|
||||
month=month,
|
||||
hospital=physician.hospital,
|
||||
department=physician.department,
|
||||
received_count=received_count,
|
||||
sent_count=sent_count,
|
||||
acknowledged_count=acknowledged_count,
|
||||
hospital_rank=random.randint(1, 10) if received_count > 5 else None,
|
||||
))
|
||||
|
||||
# Bulk create stats
|
||||
AppreciationStats.objects.bulk_create(stats)
|
||||
defaults={
|
||||
'hospital': staff_member.hospital,
|
||||
'department': staff_member.department,
|
||||
'received_count': received_count,
|
||||
'sent_count': sent_count,
|
||||
'acknowledged_count': acknowledged_count,
|
||||
'hospital_rank': random.randint(1, 10) if received_count > 5 else None,
|
||||
}
|
||||
)
|
||||
if created:
|
||||
stats.append(stat)
|
||||
|
||||
print(f" Generated {len(stats)} appreciation statistics")
|
||||
return stats
|
||||
@ -1588,36 +1598,50 @@ def main():
|
||||
print("="*60 + "\n")
|
||||
|
||||
# Clear existing data first
|
||||
# clear_existing_data()
|
||||
clear_existing_data()
|
||||
|
||||
# Create base data
|
||||
# hospitals = create_hospitals()
|
||||
hospitals = Hospital.objects.all()
|
||||
|
||||
hospitals = create_hospitals()
|
||||
departments = create_departments(hospitals)
|
||||
staff = create_staff(hospitals, departments)
|
||||
patients = create_patients(hospitals)
|
||||
# create_users(hospitals)
|
||||
users = create_users(hospitals)
|
||||
|
||||
# Get all users for assignments
|
||||
# users = list(User.objects.all())
|
||||
users_list = list(User.objects.all())
|
||||
|
||||
# Create complaint categories first
|
||||
# categories = create_complaint_categories(hospitals)
|
||||
categories = create_complaint_categories(hospitals)
|
||||
|
||||
# Create operational data
|
||||
# complaints = create_complaints(patients, hospitals, physicians, users)
|
||||
# inquiries = create_inquiries(patients, hospitals, users)
|
||||
# feedbacks = create_feedback(patients, hospitals, physicians, users)
|
||||
# create_survey_templates(hospitals)
|
||||
# create_journey_templates(hospitals)
|
||||
# projects = create_qi_projects(hospitals)
|
||||
# actions = create_px_actions(complaints, hospitals, users)
|
||||
# journey_instances = create_journey_instances(None, patients)
|
||||
# survey_instances = create_survey_instances(None, patients, physicians)
|
||||
# call_interactions = create_call_center_interactions(patients, hospitals, users)
|
||||
# social_mentions = create_social_mentions(hospitals)
|
||||
# physician_ratings = create_physician_monthly_ratings(physicians)
|
||||
complaints = create_complaints(patients, hospitals, staff, users_list)
|
||||
inquiries = create_inquiries(patients, hospitals, users_list)
|
||||
feedbacks = create_feedback(patients, hospitals, staff, users_list)
|
||||
create_survey_templates(hospitals)
|
||||
create_journey_templates(hospitals)
|
||||
projects = create_qi_projects(hospitals)
|
||||
actions = create_px_actions(complaints, hospitals, users_list)
|
||||
journey_instances = create_journey_instances(None, patients)
|
||||
survey_instances = create_survey_instances(None, patients, staff)
|
||||
call_interactions = create_call_center_interactions(patients, hospitals, users_list)
|
||||
social_mentions = create_social_mentions(hospitals)
|
||||
staff_ratings = create_staff_monthly_ratings(staff)
|
||||
|
||||
# Seed appreciation categories and badges
|
||||
print("\nSeeding appreciation data...")
|
||||
from django.core.management import call_command
|
||||
call_command('seed_appreciation_data', verbosity=0)
|
||||
print(" ✓ Appreciation categories and badges seeded")
|
||||
|
||||
# Get appreciation categories and badges
|
||||
appreciation_categories = list(AppreciationCategory.objects.filter(is_active=True))
|
||||
badges = list(AppreciationBadge.objects.filter(is_active=True))
|
||||
|
||||
# Create appreciation data
|
||||
appreciations = create_appreciations(users_list, staff, hospitals, departments, appreciation_categories)
|
||||
user_badges = award_badges(badges, users_list, staff, appreciation_categories)
|
||||
appreciation_stats = generate_appreciation_stats(users_list, staff, hospitals)
|
||||
observations = create_observations(hospitals, departments, users_list)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("Data Generation Complete!")
|
||||
@ -1627,26 +1651,26 @@ def main():
|
||||
print(f" - {len(departments)} Departments")
|
||||
print(f" - {len(staff)} Staff")
|
||||
print(f" - {len(patients)} Patients")
|
||||
# print(f" - {len(users)} Users")
|
||||
# print(f" - {len(complaints)} Complaints (2 years)")
|
||||
# print(f" - {len(inquiries)} Inquiries (2 years)")
|
||||
# print(f" - {len(feedbacks)} Feedback Items")
|
||||
# print(f" - {len(actions)} PX Actions")
|
||||
# print(f" - {len(journey_instances)} Journey Instances")
|
||||
# print(f" - {len(survey_instances)} Survey Instances")
|
||||
# print(f" - {len(call_interactions)} Call Center Interactions")
|
||||
# print(f" - {len(social_mentions)} Social Media Mentions")
|
||||
# print(f" - {len(projects)} QI Projects")
|
||||
# print(f" - {len(staff_ratings)} Staff Monthly Ratings")
|
||||
# print(f" - {len(appreciations)} Appreciations (2 years)")
|
||||
# print(f" - {len(user_badges)} Badges Awarded")
|
||||
# print(f" - {len(appreciation_stats)} Appreciation Statistics")
|
||||
# print(f" - {len(observations)} Observations (2 years)")
|
||||
print(f" - {len(users_list)} Users")
|
||||
print(f" - {len(complaints)} Complaints (2 years)")
|
||||
print(f" - {len(inquiries)} Inquiries (2 years)")
|
||||
print(f" - {len(feedbacks)} Feedback Items")
|
||||
print(f" - {len(actions)} PX Actions")
|
||||
print(f" - {len(journey_instances)} Journey Instances")
|
||||
print(f" - {len(survey_instances)} Survey Instances")
|
||||
print(f" - {len(call_interactions)} Call Center Interactions")
|
||||
print(f" - {len(social_mentions)} Social Media Mentions")
|
||||
print(f" - {len(projects)} QI Projects")
|
||||
print(f" - {len(staff_ratings)} Staff Monthly Ratings")
|
||||
print(f" - {len(appreciations)} Appreciations (2 years)")
|
||||
print(f" - {len(user_badges)} Badges Awarded")
|
||||
print(f" - {len(appreciation_stats)} Appreciation Statistics")
|
||||
print(f" - {len(observations)} Observations (2 years)")
|
||||
print(f"\nYou can now login with:")
|
||||
print(f" Username: px_admin")
|
||||
print(f" Password: admin123")
|
||||
print(f"\nOr hospital admins:")
|
||||
print(f" Username: admin_kfmc (or admin_kfsh, admin_kamc, etc.)")
|
||||
print(f" Username: admin_hh")
|
||||
print(f" Password: admin123")
|
||||
print(f"\nAccess the system at: http://127.0.0.1:8000/")
|
||||
print("\n")
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -7,10 +7,10 @@
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.action-header {
|
||||
background: linear-gradient(135deg, #f57c00 0%, #ff6f00 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 12px;
|
||||
{#background: linear-gradient(135deg, #616161 0%, #616161 100%);#}
|
||||
{#color: white;#}
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.sla-progress-card {
|
||||
@ -156,7 +156,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Action Header -->
|
||||
<div class="action-header">
|
||||
<div class="card p-2">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
|
||||
@ -122,7 +122,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Complaint Header -->
|
||||
<div class="complaint-header">
|
||||
<div class="card p-3">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
|
||||
@ -109,7 +109,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Inquiry Header -->
|
||||
<div class="inquiry-header">
|
||||
<div class="card p-3">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
|
||||
@ -151,7 +151,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for rating in top_physicians %}
|
||||
<tr onclick="window.location='{% url 'physicians:physician_detail' rating.physician.id %}'" style="cursor: pointer;">
|
||||
<tr onclick="window.location=''" style="cursor: pointer;">
|
||||
<td>
|
||||
{% if forloop.counter == 1 %}
|
||||
<h4 class="mb-0"><i class="bi bi-trophy-fill text-warning"></i></h4>
|
||||
|
||||
@ -11,6 +11,120 @@
|
||||
<ul class="nav flex-column">
|
||||
<!-- Command Center -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'command_center' %}active{% endif %}"
|
||||
href="{% url 'analytics:command_center' %}">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
{% trans "Command Center" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<hr class="my-2" style="border-color: rgba(255,255,255,0.1);">
|
||||
|
||||
<!-- Complaints -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if 'complaints' in request.path %}active{% endif %}"
|
||||
data-bs-toggle="collapse"
|
||||
href="#complaintsMenu"
|
||||
role="button"
|
||||
aria-expanded="{% if 'complaints' in request.path %}true{% else %}false{% endif %}"
|
||||
aria-controls="complaintsMenu">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
{% trans "Complaints" %}
|
||||
<span class="badge bg-danger">{{ complaint_count|default:0 }}</span>
|
||||
<i class="bi bi-chevron-down ms-auto"></i>
|
||||
</a>
|
||||
<div class="collapse {% if 'complaints' in request.path %}show{% endif %}" id="complaintsMenu">
|
||||
<ul class="nav flex-column ms-3">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'complaint_list' %}active{% endif %}"
|
||||
href="{% url 'complaints:complaint_list' %}">
|
||||
<i class="bi bi-list-ul"></i>
|
||||
{% trans "All Complaints" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'inquiry_list' %}active{% endif %}"
|
||||
href="{% url 'complaints:inquiry_list' %}">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
{% trans "Inquiries" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'complaints_analytics' %}active{% endif %}"
|
||||
href="{% url 'complaints:complaints_analytics' %}">
|
||||
<i class="bi bi-bar-chart"></i>
|
||||
{% trans "Analytics" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- Feedback -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if 'feedback' in request.path %}active{% endif %}"
|
||||
href="{% url 'feedback:feedback_list' %}">
|
||||
<i class="bi bi-chat-heart"></i>
|
||||
{% trans "Feedback" %}
|
||||
<span class="badge bg-success">{{ feedback_count|default:0 }}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Appreciation -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if 'appreciation' in request.path %}active{% endif %}"
|
||||
data-bs-toggle="collapse"
|
||||
href="#appreciationMenu"
|
||||
role="button"
|
||||
aria-expanded="{% if 'appreciation' in request.path %}true{% else %}false{% endif %}"
|
||||
aria-controls="appreciationMenu">
|
||||
<i class="bi bi-heart-fill"></i>
|
||||
{% trans "Appreciation" %}
|
||||
<i class="bi bi-chevron-down ms-auto"></i>
|
||||
</a>
|
||||
<div class="collapse {% if 'appreciation' in request.path %}show{% endif %}" id="appreciationMenu">
|
||||
<ul class="nav flex-column ms-3">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'appreciation_list' %}active{% endif %}"
|
||||
href="{% url 'appreciation:appreciation_list' %}">
|
||||
<i class="bi bi-list-ul"></i>
|
||||
{% trans "All Appreciations" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'appreciation_send' %}active{% endif %}"
|
||||
href="{% url 'appreciation:appreciation_send' %}">
|
||||
<i class="bi bi-send"></i>
|
||||
{% trans "Send Appreciation" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'leaderboard_view' %}active{% endif %}"
|
||||
href="{% url 'appreciation:leaderboard_view' %}">
|
||||
<i class="bi bi-trophy"></i>
|
||||
{% trans "Leaderboard" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'my_badges_view' %}active{% endif %}"
|
||||
href="{% url 'appreciation:my_badges_view' %}">
|
||||
<i class="bi bi-award"></i>
|
||||
{% trans "My Badges" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- Observations -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if 'observations' in request.path and 'new' not in request.path %}active{% endif %}"
|
||||
href="{% url 'observations:observation_list' %}">
|
||||
<i class="bi bi-eye"></i>
|
||||
{% trans "Observations" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- PX Actions -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if 'actions' in request.path %}active{% endif %}"
|
||||
@ -165,7 +279,11 @@
|
||||
{% if user.is_px_admin %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if 'config' in request.path %}active{% endif %}"
|
||||
href="{% url 'config:dashboard' %}">
|
||||
data-bs-toggle="collapse"
|
||||
href="#settingsMenu"
|
||||
role="button"
|
||||
aria-expanded="{% if 'config' in request.path %}true{% else %}false{% endif %}"
|
||||
aria-controls="settingsMenu">
|
||||
<i class="bi bi-gear"></i>
|
||||
{% trans "Settings" %}
|
||||
<i class="bi bi-chevron-down ms-auto"></i>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user