From 1ae0e763be6e04e5edf74e94fcca136770c10a7f Mon Sep 17 00:00:00 2001 From: Marwan Alwali Date: Thu, 8 Jan 2026 10:34:08 +0300 Subject: [PATCH] update --- apps/accounts/migrations/0001_initial.py | 85 +- apps/accounts/migrations/0002_initial.py | 59 +- ...user_acknowledgement_completed_and_more.py | 160 - apps/ai_engine/migrations/0001_initial.py | 2 +- apps/analytics/migrations/0001_initial.py | 2 +- apps/analytics/services/analytics_service.py | 18 +- apps/appreciation/migrations/0001_initial.py | 2 +- apps/callcenter/migrations/0001_initial.py | 2 +- .../commands/seed_complaint_configs.py | 2 +- apps/complaints/migrations/0001_initial.py | 36 +- apps/complaints/migrations/0002_initial.py | 32 +- ...lter_complaintcategory_options_and_more.py | 31 - .../0004_inquiryattachment_inquiryupdate.py | 54 - apps/core/ai_service.py | 5 +- apps/core/migrations/0001_initial.py | 2 +- apps/feedback/migrations/0001_initial.py | 2 +- apps/feedback/migrations/0002_initial.py | 2 +- apps/integrations/migrations/0001_initial.py | 2 +- apps/journeys/migrations/0001_initial.py | 2 +- apps/journeys/migrations/0002_initial.py | 2 +- apps/notifications/migrations/0001_initial.py | 2 +- apps/observations/migrations/0001_initial.py | 11 +- .../migrations/0002_add_missing_fields.py | 65 - ...observation_hospita_dcd21a_idx_and_more.py | 36 - apps/organizations/migrations/0001_initial.py | 6 +- .../migrations/0002_hospital_metadata.py | 18 - .../migrations/0003_patient_department.py | 19 - .../0004_remove_patient_department.py | 17 - .../migrations/0005_alter_staff_department.py | 19 - apps/physicians/migrations/0001_initial.py | 2 +- apps/projects/migrations/0001_initial.py | 2 +- apps/projects/migrations/0002_initial.py | 2 +- .../migrations/0001_initial.py | 2 +- apps/references/migrations/0001_initial.py | 4 +- apps/social/migrations/0001_initial.py | 2 +- apps/standards/migrations/0001_initial.py | 4 +- apps/surveys/migrations/0001_initial.py | 2 +- generate_saudi_data.py | 236 +- locale/ar/LC_MESSAGES/django.mo | Bin 70286 -> 99314 bytes locale/ar/LC_MESSAGES/django.po | 3356 +++++++++++++---- templates/actions/action_detail.html | 10 +- templates/complaints/complaint_detail.html | 2 +- templates/complaints/inquiry_detail.html | 2 +- templates/dashboard/command_center.html | 2 +- templates/layouts/partials/sidebar.html | 120 +- 45 files changed, 3055 insertions(+), 1388 deletions(-) delete mode 100644 apps/accounts/migrations/0003_user_acknowledgement_completed_and_more.py delete mode 100644 apps/complaints/migrations/0003_alter_complaintcategory_options_and_more.py delete mode 100644 apps/complaints/migrations/0004_inquiryattachment_inquiryupdate.py delete mode 100644 apps/observations/migrations/0002_add_missing_fields.py delete mode 100644 apps/observations/migrations/0003_rename_obs_hospital_status_idx_observation_hospita_dcd21a_idx_and_more.py delete mode 100644 apps/organizations/migrations/0002_hospital_metadata.py delete mode 100644 apps/organizations/migrations/0003_patient_department.py delete mode 100644 apps/organizations/migrations/0004_remove_patient_department.py delete mode 100644 apps/organizations/migrations/0005_alter_staff_department.py diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py index 429fd71..76b52b5 100644 --- a/apps/accounts/migrations/0001_initial.py +++ b/apps/accounts/migrations/0001_initial.py @@ -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'], + }, + ), ] diff --git a/apps/accounts/migrations/0002_initial.py b/apps/accounts/migrations/0002_initial.py index 79d1639..851a617 100644 --- a/apps/accounts/migrations/0002_initial.py +++ b/apps/accounts/migrations/0002_initial.py @@ -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'), + ), ] diff --git a/apps/accounts/migrations/0003_user_acknowledgement_completed_and_more.py b/apps/accounts/migrations/0003_user_acknowledgement_completed_and_more.py deleted file mode 100644 index b0f8e64..0000000 --- a/apps/accounts/migrations/0003_user_acknowledgement_completed_and_more.py +++ /dev/null @@ -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'), - ), - ] diff --git a/apps/ai_engine/migrations/0001_initial.py b/apps/ai_engine/migrations/0001_initial.py index 3c06d80..549c9c6 100644 --- a/apps/ai_engine/migrations/0001_initial.py +++ b/apps/ai_engine/migrations/0001_initial.py @@ -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 diff --git a/apps/analytics/migrations/0001_initial.py b/apps/analytics/migrations/0001_initial.py index 48cf263..0ed9670 100644 --- a/apps/analytics/migrations/0001_initial.py +++ b/apps/analytics/migrations/0001_initial.py @@ -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 diff --git a/apps/analytics/services/analytics_service.py b/apps/analytics/services/analytics_service.py index d607fdc..587ad37 100644 --- a/apps/analytics/services/analytics_service.py +++ b/apps/analytics/services/analytics_service.py @@ -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, diff --git a/apps/appreciation/migrations/0001_initial.py b/apps/appreciation/migrations/0001_initial.py index 34c483e..c5853f1 100644 --- a/apps/appreciation/migrations/0001_initial.py +++ b/apps/appreciation/migrations/0001_initial.py @@ -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 diff --git a/apps/callcenter/migrations/0001_initial.py b/apps/callcenter/migrations/0001_initial.py index b93353c..4eda9f9 100644 --- a/apps/callcenter/migrations/0001_initial.py +++ b/apps/callcenter/migrations/0001_initial.py @@ -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 diff --git a/apps/complaints/management/commands/seed_complaint_configs.py b/apps/complaints/management/commands/seed_complaint_configs.py index f85c990..8c149fc 100644 --- a/apps/complaints/management/commands/seed_complaint_configs.py +++ b/apps/complaints/management/commands/seed_complaint_configs.py @@ -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'], diff --git a/apps/complaints/migrations/0001_initial.py b/apps/complaints/migrations/0001_initial.py index 6e3b2f1..dffb40d 100644 --- a/apps/complaints/migrations/0001_initial.py +++ b/apps/complaints/migrations/0001_initial.py @@ -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=[ diff --git a/apps/complaints/migrations/0002_initial.py b/apps/complaints/migrations/0002_initial.py index b84e90e..2bc9119 100644 --- a/apps/complaints/migrations/0002_initial.py +++ b/apps/complaints/migrations/0002_initial.py @@ -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'), + ), ] diff --git a/apps/complaints/migrations/0003_alter_complaintcategory_options_and_more.py b/apps/complaints/migrations/0003_alter_complaintcategory_options_and_more.py deleted file mode 100644 index 86810bb..0000000 --- a/apps/complaints/migrations/0003_alter_complaintcategory_options_and_more.py +++ /dev/null @@ -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'), - ), - ] diff --git a/apps/complaints/migrations/0004_inquiryattachment_inquiryupdate.py b/apps/complaints/migrations/0004_inquiryattachment_inquiryupdate.py deleted file mode 100644 index 2b49018..0000000 --- a/apps/complaints/migrations/0004_inquiryattachment_inquiryupdate.py +++ /dev/null @@ -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')], - }, - ), - ] diff --git a/apps/core/ai_service.py b/apps/core/ai_service.py index 2c8bbdd..87c09cb 100644 --- a/apps/core/ai_service.py +++ b/apps/core/ai_service.py @@ -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" diff --git a/apps/core/migrations/0001_initial.py b/apps/core/migrations/0001_initial.py index 6f592d2..6023124 100644 --- a/apps/core/migrations/0001_initial.py +++ b/apps/core/migrations/0001_initial.py @@ -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 diff --git a/apps/feedback/migrations/0001_initial.py b/apps/feedback/migrations/0001_initial.py index 0897612..47e2dcb 100644 --- a/apps/feedback/migrations/0001_initial.py +++ b/apps/feedback/migrations/0001_initial.py @@ -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 diff --git a/apps/feedback/migrations/0002_initial.py b/apps/feedback/migrations/0002_initial.py index c8da2bd..a09fdb4 100644 --- a/apps/feedback/migrations/0002_initial.py +++ b/apps/feedback/migrations/0002_initial.py @@ -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 diff --git a/apps/integrations/migrations/0001_initial.py b/apps/integrations/migrations/0001_initial.py index 313675a..b9cf469 100644 --- a/apps/integrations/migrations/0001_initial.py +++ b/apps/integrations/migrations/0001_initial.py @@ -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 diff --git a/apps/journeys/migrations/0001_initial.py b/apps/journeys/migrations/0001_initial.py index b32bb12..255c859 100644 --- a/apps/journeys/migrations/0001_initial.py +++ b/apps/journeys/migrations/0001_initial.py @@ -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 diff --git a/apps/journeys/migrations/0002_initial.py b/apps/journeys/migrations/0002_initial.py index 2bc527f..5dc83f0 100644 --- a/apps/journeys/migrations/0002_initial.py +++ b/apps/journeys/migrations/0002_initial.py @@ -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 diff --git a/apps/notifications/migrations/0001_initial.py b/apps/notifications/migrations/0001_initial.py index 36d3cbe..852ad88 100644 --- a/apps/notifications/migrations/0001_initial.py +++ b/apps/notifications/migrations/0001_initial.py @@ -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 diff --git a/apps/observations/migrations/0001_initial.py b/apps/observations/migrations/0001_initial.py index aa375e5..c462907 100644 --- a/apps/observations/migrations/0001_initial.py +++ b/apps/observations/migrations/0001_initial.py @@ -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'), diff --git a/apps/observations/migrations/0002_add_missing_fields.py b/apps/observations/migrations/0002_add_missing_fields.py deleted file mode 100644 index b247048..0000000 --- a/apps/observations/migrations/0002_add_missing_fields.py +++ /dev/null @@ -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'), - ), - ] diff --git a/apps/observations/migrations/0003_rename_obs_hospital_status_idx_observation_hospita_dcd21a_idx_and_more.py b/apps/observations/migrations/0003_rename_obs_hospital_status_idx_observation_hospita_dcd21a_idx_and_more.py deleted file mode 100644 index 5eff108..0000000 --- a/apps/observations/migrations/0003_rename_obs_hospital_status_idx_observation_hospita_dcd21a_idx_and_more.py +++ /dev/null @@ -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'), - ), - ] diff --git a/apps/organizations/migrations/0001_initial.py b/apps/organizations/migrations/0001_initial.py index e55baef..ae2ed2a 100644 --- a/apps/organizations/migrations/0001_initial.py +++ b/apps/organizations/migrations/0001_initial.py @@ -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)), ], diff --git a/apps/organizations/migrations/0002_hospital_metadata.py b/apps/organizations/migrations/0002_hospital_metadata.py deleted file mode 100644 index 080c103..0000000 --- a/apps/organizations/migrations/0002_hospital_metadata.py +++ /dev/null @@ -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), - ), - ] diff --git a/apps/organizations/migrations/0003_patient_department.py b/apps/organizations/migrations/0003_patient_department.py deleted file mode 100644 index 7d2b2b6..0000000 --- a/apps/organizations/migrations/0003_patient_department.py +++ /dev/null @@ -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'), - ), - ] diff --git a/apps/organizations/migrations/0004_remove_patient_department.py b/apps/organizations/migrations/0004_remove_patient_department.py deleted file mode 100644 index 9df7076..0000000 --- a/apps/organizations/migrations/0004_remove_patient_department.py +++ /dev/null @@ -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', - ), - ] diff --git a/apps/organizations/migrations/0005_alter_staff_department.py b/apps/organizations/migrations/0005_alter_staff_department.py deleted file mode 100644 index a5b2afd..0000000 --- a/apps/organizations/migrations/0005_alter_staff_department.py +++ /dev/null @@ -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'), - ), - ] diff --git a/apps/physicians/migrations/0001_initial.py b/apps/physicians/migrations/0001_initial.py index b09a2bc..c186a05 100644 --- a/apps/physicians/migrations/0001_initial.py +++ b/apps/physicians/migrations/0001_initial.py @@ -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 diff --git a/apps/projects/migrations/0001_initial.py b/apps/projects/migrations/0001_initial.py index 0bb7c78..1e0714e 100644 --- a/apps/projects/migrations/0001_initial.py +++ b/apps/projects/migrations/0001_initial.py @@ -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 diff --git a/apps/projects/migrations/0002_initial.py b/apps/projects/migrations/0002_initial.py index a18d597..2f486e7 100644 --- a/apps/projects/migrations/0002_initial.py +++ b/apps/projects/migrations/0002_initial.py @@ -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 diff --git a/apps/px_action_center/migrations/0001_initial.py b/apps/px_action_center/migrations/0001_initial.py index ed007b2..5c8eb01 100644 --- a/apps/px_action_center/migrations/0001_initial.py +++ b/apps/px_action_center/migrations/0001_initial.py @@ -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 diff --git a/apps/references/migrations/0001_initial.py b/apps/references/migrations/0001_initial.py index 8756976..7c8382f 100644 --- a/apps/references/migrations/0001_initial.py +++ b/apps/references/migrations/0001_initial.py @@ -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), ] diff --git a/apps/social/migrations/0001_initial.py b/apps/social/migrations/0001_initial.py index 0e1149b..40076f3 100644 --- a/apps/social/migrations/0001_initial.py +++ b/apps/social/migrations/0001_initial.py @@ -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 diff --git a/apps/standards/migrations/0001_initial.py b/apps/standards/migrations/0001_initial.py index efffe83..1fff82e 100644 --- a/apps/standards/migrations/0001_initial.py +++ b/apps/standards/migrations/0001_initial.py @@ -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), ] diff --git a/apps/surveys/migrations/0001_initial.py b/apps/surveys/migrations/0001_initial.py index ad76f54..fab22e8 100644 --- a/apps/surveys/migrations/0001_initial.py +++ b/apps/surveys/migrations/0001_initial.py @@ -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 diff --git a/generate_saudi_data.py b/generate_saudi_data.py index bd02df4..05cd8c9 100644 --- a/generate_saudi_data.py +++ b/generate_saudi_data.py @@ -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") diff --git a/locale/ar/LC_MESSAGES/django.mo b/locale/ar/LC_MESSAGES/django.mo index 5204c5176b873c7564e966ea22b3595112655dd9..e9d7f40d78368c16cecaf13830da790aaa41afe5 100644 GIT binary patch literal 99314 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 delta 21871 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 .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 @@ -
+
diff --git a/templates/complaints/complaint_detail.html b/templates/complaints/complaint_detail.html index 08a2438..d80eaf3 100644 --- a/templates/complaints/complaint_detail.html +++ b/templates/complaints/complaint_detail.html @@ -122,7 +122,7 @@
-
+
diff --git a/templates/complaints/inquiry_detail.html b/templates/complaints/inquiry_detail.html index 0f66761..e947d1f 100644 --- a/templates/complaints/inquiry_detail.html +++ b/templates/complaints/inquiry_detail.html @@ -109,7 +109,7 @@
-
+
diff --git a/templates/dashboard/command_center.html b/templates/dashboard/command_center.html index 4d88dd3..7906443 100644 --- a/templates/dashboard/command_center.html +++ b/templates/dashboard/command_center.html @@ -151,7 +151,7 @@ {% for rating in top_physicians %} - + {% if forloop.counter == 1 %}

diff --git a/templates/layouts/partials/sidebar.html b/templates/layouts/partials/sidebar.html index 2e1b37e..8ecbaa8 100644 --- a/templates/layouts/partials/sidebar.html +++ b/templates/layouts/partials/sidebar.html @@ -11,6 +11,120 @@