update-po-file

This commit is contained in:
Marwan Alwali 2026-01-18 14:04:23 +03:00
parent 1f9d8a7198
commit 524efbead9
41 changed files with 3269 additions and 686 deletions

View File

@ -1,24 +1,42 @@
"""
Accounts admin
"""
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import UserChangeForm as BaseUserChangeForm
from django.utils.translation import gettext_lazy as _
from .models import Role, User
class UserChangeForm(BaseUserChangeForm):
"""Custom user change form that handles nullable username field."""
# Override username field to use a regular CharField that handles None
username = forms.CharField(
max_length=150,
required=False,
help_text=_('Optional. 150 characters or fewer.'),
)
class Meta(BaseUserChangeForm.Meta):
model = User
@admin.register(User)
class UserAdmin(BaseUserAdmin):
"""Custom User admin"""
form = UserChangeForm
list_display = ['email', 'username', 'first_name', 'last_name', 'hospital', 'department', 'is_active', 'is_staff']
list_filter = ['is_active', 'is_staff', 'is_superuser', 'groups', 'hospital', 'department']
search_fields = ['email', 'username', 'first_name', 'last_name', 'employee_id']
ordering = ['-date_joined']
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email', 'phone', 'employee_id')}),
(None, {'fields': ('email', 'username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'phone', 'employee_id')}),
(_('Organization'), {'fields': ('hospital', 'department')}),
(_('Profile'), {'fields': ('avatar', 'bio', 'language')}),
(_('Permissions'), {

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.utils.timezone
import uuid
@ -33,6 +33,10 @@ class Migration(migrations.Migration):
('avatar', models.ImageField(blank=True, null=True, upload_to='avatars/')),
('bio', models.TextField(blank=True)),
('language', models.CharField(choices=[('en', 'English'), ('ar', 'Arabic')], default='en', max_length=5)),
('notification_email_enabled', models.BooleanField(default=True, help_text='Enable email notifications')),
('notification_sms_enabled', models.BooleanField(default=False, help_text='Enable SMS notifications')),
('preferred_notification_channel', models.CharField(choices=[('email', 'Email'), ('sms', 'SMS'), ('both', 'Both')], default='email', help_text='Preferred notification channel for general notifications', max_length=10)),
('explanation_notification_channel', models.CharField(choices=[('email', 'Email'), ('sms', 'SMS'), ('both', 'Both')], default='email', help_text='Preferred channel for explanation requests', max_length=10)),
('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)),

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
from django.conf import settings

View File

@ -1,33 +0,0 @@
# Generated by Django 6.0.1 on 2026-01-12 18:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='explanation_notification_channel',
field=models.CharField(choices=[('email', 'Email'), ('sms', 'SMS'), ('both', 'Both')], default='email', help_text='Preferred channel for explanation requests', max_length=10),
),
migrations.AddField(
model_name='user',
name='notification_email_enabled',
field=models.BooleanField(default=True, help_text='Enable email notifications'),
),
migrations.AddField(
model_name='user',
name='notification_sms_enabled',
field=models.BooleanField(default=False, help_text='Enable SMS notifications'),
),
migrations.AddField(
model_name='user',
name='preferred_notification_channel',
field=models.CharField(choices=[('email', 'Email'), ('sms', 'SMS'), ('both', 'Both')], default='email', help_text='Preferred notification channel for general notifications', max_length=10),
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -229,8 +229,9 @@ class UnifiedAnalyticsService:
'avg_survey_score': float(surveys_qs.aggregate(avg=Avg('total_score'))['avg'] or 0),
# Social Media KPIs
# Sentiment is stored in ai_analysis JSON field as ai_analysis.sentiment
'negative_social_comments': int(SocialMediaComment.objects.filter(
sentiment='negative',
ai_analysis__sentiment='negative',
published_at__gte=start_date,
published_at__lte=end_date
).count()),

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -49,8 +49,7 @@ def send_appreciation_notification(appreciation):
Uses the notification system to send email/SMS/WhatsApp.
"""
try:
from apps.notifications.models import NotificationLog, NotificationChannel, NotificationStatus
from apps.notifications.services import send_notification
from apps.notifications.services import send_email, send_sms
# Get recipient details
recipient_email = appreciation.get_recipient_email()
@ -73,12 +72,11 @@ def send_appreciation_notification(appreciation):
# Send email if available
if recipient_email:
try:
send_notification(
channel=NotificationChannel.EMAIL,
recipient=recipient_email,
send_email(
email=recipient_email,
subject=f"New Appreciation Received - {appreciation.hospital.name}",
message=message_en,
content_object=appreciation,
related_object=appreciation,
)
except Exception as e:
# Log error but don't fail
@ -87,11 +85,10 @@ def send_appreciation_notification(appreciation):
# Send SMS if available
if recipient_phone:
try:
send_notification(
channel=NotificationChannel.SMS,
recipient=recipient_phone,
send_sms(
phone=recipient_phone,
message=message_en,
content_object=appreciation,
related_object=appreciation,
)
except Exception as e:
# Log error but don't fail

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid
@ -64,6 +64,10 @@ class Migration(migrations.Migration):
('email_sent_at', models.DateTimeField(blank=True, help_text='When the explanation request email was sent', null=True)),
('responded_at', models.DateTimeField(blank=True, help_text='When the explanation was submitted', null=True)),
('request_message', models.TextField(blank=True, help_text='Optional message sent with the explanation request')),
('sla_due_at', models.DateTimeField(blank=True, db_index=True, help_text='SLA deadline for staff to submit explanation', null=True)),
('is_overdue', models.BooleanField(db_index=True, default=False, help_text='Explanation request is overdue')),
('reminder_sent_at', models.DateTimeField(blank=True, help_text='Reminder sent to staff about overdue explanation', null=True)),
('escalated_at', models.DateTimeField(blank=True, help_text='When explanation was escalated to manager', null=True)),
],
options={
'verbose_name': 'Complaint Explanation',
@ -80,7 +84,10 @@ class Migration(migrations.Migration):
('severity', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('critical', 'Critical')], help_text='Severity level for this SLA', max_length=20)),
('priority', models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('critical', 'Critical')], help_text='Priority level for this SLA', max_length=20)),
('sla_hours', models.IntegerField(help_text='Number of hours until SLA deadline')),
('reminder_hours_before', models.IntegerField(default=24, help_text='Send reminder X hours before deadline')),
('reminder_hours_before', models.IntegerField(default=24, help_text='Send first reminder X hours before deadline')),
('second_reminder_enabled', models.BooleanField(default=False, help_text='Enable sending a second reminder')),
('second_reminder_hours_before', models.IntegerField(default=6, help_text='Send second reminder X hours before deadline')),
('thank_you_email_enabled', models.BooleanField(default=False, help_text='Send thank you email when complaint is closed')),
('is_active', models.BooleanField(default=True)),
],
options={
@ -127,9 +134,13 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=200)),
('description', models.TextField(blank=True)),
('escalation_level', models.IntegerField(default=1, help_text='Escalation level (1 = first level, 2 = second, etc.)')),
('max_escalation_level', models.IntegerField(default=3, help_text='Maximum escalation level before stopping (default: 3)')),
('trigger_on_overdue', models.BooleanField(default=True, help_text='Trigger when complaint is overdue')),
('trigger_hours_overdue', models.IntegerField(default=0, help_text='Trigger X hours after overdue (0 = immediately)')),
('escalate_to_role', models.CharField(choices=[('department_manager', 'Department Manager'), ('hospital_admin', 'Hospital Admin'), ('px_admin', 'PX Admin'), ('specific_user', 'Specific User')], help_text='Role to escalate to', max_length=50)),
('reminder_escalation_enabled', models.BooleanField(default=False, help_text='Enable escalation after reminder if no action taken')),
('reminder_escalation_hours', models.IntegerField(default=24, help_text='Escalate X hours after reminder if no action')),
('escalate_to_role', models.CharField(choices=[('department_manager', 'Department Manager'), ('hospital_admin', 'Hospital Admin'), ('px_admin', 'PX Admin'), ('ceo', 'CEO'), ('specific_user', 'Specific User')], help_text='Role to escalate to', max_length=50)),
('severity_filter', models.CharField(blank=True, choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('critical', 'Critical')], help_text='Only escalate complaints with this severity (blank = all)', max_length=20)),
('priority_filter', models.CharField(blank=True, choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High'), ('critical', 'Critical')], help_text='Only escalate complaints with this priority (blank = all)', max_length=20)),
('order', models.IntegerField(default=0, help_text='Escalation order (lower = first)')),
@ -157,6 +168,25 @@ class Migration(migrations.Migration):
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='ExplanationSLAConfig',
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)),
('response_hours', models.IntegerField(default=48, help_text='Hours staff has to submit explanation')),
('reminder_hours_before', models.IntegerField(default=12, help_text='Send reminder X hours before deadline')),
('auto_escalate_enabled', models.BooleanField(default=True, help_text='Automatically escalate to manager if no response')),
('escalation_hours_overdue', models.IntegerField(default=0, help_text='Escalate X hours after overdue (0 = immediately)')),
('max_escalation_levels', models.IntegerField(default=3, help_text='Maximum levels to escalate up staff hierarchy')),
('is_active', models.BooleanField(default=True)),
],
options={
'verbose_name': 'Explanation SLA Config',
'verbose_name_plural': 'Explanation SLA Configs',
'ordering': ['hospital'],
},
),
migrations.CreateModel(
name='Inquiry',
fields=[
@ -230,7 +260,8 @@ class Migration(migrations.Migration):
('assigned_at', models.DateTimeField(blank=True, null=True)),
('due_at', models.DateTimeField(db_index=True, help_text='SLA deadline')),
('is_overdue', models.BooleanField(db_index=True, default=False)),
('reminder_sent_at', models.DateTimeField(blank=True, null=True)),
('reminder_sent_at', models.DateTimeField(blank=True, help_text='First SLA reminder timestamp', null=True)),
('second_reminder_sent_at', models.DateTimeField(blank=True, help_text='Second SLA reminder timestamp', null=True)),
('escalated_at', models.DateTimeField(blank=True, null=True)),
('resolution', models.TextField(blank=True)),
('resolved_at', models.DateTimeField(blank=True, null=True)),

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
from django.conf import settings

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
from django.conf import settings
@ -57,6 +57,11 @@ class Migration(migrations.Migration):
name='complaint',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='explanations', to='complaints.complaint'),
),
migrations.AddField(
model_name='complaintexplanation',
name='escalated_to_manager',
field=models.ForeignKey(blank=True, help_text="Escalated to this explanation (manager's explanation request)", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='escalated_from_staff', to='complaints.complaintexplanation'),
),
migrations.AddField(
model_name='complaintexplanation',
name='requested_by',
@ -102,6 +107,11 @@ class Migration(migrations.Migration):
name='explanation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='complaints.complaintexplanation'),
),
migrations.AddField(
model_name='explanationslaconfig',
name='hospital',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='explanation_sla_configs', to='organizations.hospital'),
),
migrations.AddField(
model_name='inquiry',
name='assigned_to',
@ -204,6 +214,10 @@ class Migration(migrations.Migration):
model_name='escalationrule',
index=models.Index(fields=['hospital', 'is_active'], name='complaints__hospita_3c8bac_idx'),
),
migrations.AddIndex(
model_name='explanationslaconfig',
index=models.Index(fields=['hospital', 'is_active'], name='complaints__hospita_fe4ec5_idx'),
),
migrations.AddIndex(
model_name='inquiry',
index=models.Index(fields=['status', '-created_at'], name='complaints__status_3d0678_idx'),

View File

@ -1,68 +0,0 @@
# Generated by Django 6.0.1 on 2026-01-13 20:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('complaints', '0003_initial'),
]
operations = [
migrations.AddField(
model_name='complaint',
name='second_reminder_sent_at',
field=models.DateTimeField(blank=True, help_text='Second SLA reminder timestamp', null=True),
),
migrations.AddField(
model_name='complaintslaconfig',
name='second_reminder_enabled',
field=models.BooleanField(default=False, help_text='Enable sending a second reminder'),
),
migrations.AddField(
model_name='complaintslaconfig',
name='second_reminder_hours_before',
field=models.IntegerField(default=6, help_text='Send second reminder X hours before deadline'),
),
migrations.AddField(
model_name='complaintslaconfig',
name='thank_you_email_enabled',
field=models.BooleanField(default=False, help_text='Send thank you email when complaint is closed'),
),
migrations.AddField(
model_name='escalationrule',
name='escalation_level',
field=models.IntegerField(default=1, help_text='Escalation level (1 = first level, 2 = second, etc.)'),
),
migrations.AddField(
model_name='escalationrule',
name='max_escalation_level',
field=models.IntegerField(default=3, help_text='Maximum escalation level before stopping (default: 3)'),
),
migrations.AddField(
model_name='escalationrule',
name='reminder_escalation_enabled',
field=models.BooleanField(default=False, help_text='Enable escalation after reminder if no action taken'),
),
migrations.AddField(
model_name='escalationrule',
name='reminder_escalation_hours',
field=models.IntegerField(default=24, help_text='Escalate X hours after reminder if no action'),
),
migrations.AlterField(
model_name='complaint',
name='reminder_sent_at',
field=models.DateTimeField(blank=True, help_text='First SLA reminder timestamp', null=True),
),
migrations.AlterField(
model_name='complaintslaconfig',
name='reminder_hours_before',
field=models.IntegerField(default=24, help_text='Send first reminder X hours before deadline'),
),
migrations.AlterField(
model_name='escalationrule',
name='escalate_to_role',
field=models.CharField(choices=[('department_manager', 'Department Manager'), ('hospital_admin', 'Hospital Admin'), ('px_admin', 'PX Admin'), ('ceo', 'CEO'), ('specific_user', 'Specific User')], help_text='Role to escalate to', max_length=50),
),
]

View File

@ -1,62 +0,0 @@
# Generated by Django 6.0.1 on 2026-01-14 12:36
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('complaints', '0004_add_second_reminder_sent_at'),
('organizations', '0004_staff_location_staff_name_staff_phone'),
]
operations = [
migrations.AddField(
model_name='complaintexplanation',
name='escalated_at',
field=models.DateTimeField(blank=True, help_text='When explanation was escalated to manager', null=True),
),
migrations.AddField(
model_name='complaintexplanation',
name='escalated_to_manager',
field=models.ForeignKey(blank=True, help_text="Escalated to this explanation (manager's explanation request)", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='escalated_from_staff', to='complaints.complaintexplanation'),
),
migrations.AddField(
model_name='complaintexplanation',
name='is_overdue',
field=models.BooleanField(db_index=True, default=False, help_text='Explanation request is overdue'),
),
migrations.AddField(
model_name='complaintexplanation',
name='reminder_sent_at',
field=models.DateTimeField(blank=True, help_text='Reminder sent to staff about overdue explanation', null=True),
),
migrations.AddField(
model_name='complaintexplanation',
name='sla_due_at',
field=models.DateTimeField(blank=True, db_index=True, help_text='SLA deadline for staff to submit explanation', null=True),
),
migrations.CreateModel(
name='ExplanationSLAConfig',
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)),
('response_hours', models.IntegerField(default=48, help_text='Hours staff has to submit explanation')),
('reminder_hours_before', models.IntegerField(default=12, help_text='Send reminder X hours before deadline')),
('auto_escalate_enabled', models.BooleanField(default=True, help_text='Automatically escalate to manager if no response')),
('escalation_hours_overdue', models.IntegerField(default=0, help_text='Escalate X hours after overdue (0 = immediately)')),
('max_escalation_levels', models.IntegerField(default=3, help_text='Maximum levels to escalate up staff hierarchy')),
('is_active', models.BooleanField(default=True)),
('hospital', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='explanation_sla_configs', to='organizations.hospital')),
],
options={
'verbose_name': 'Explanation SLA Config',
'verbose_name_plural': 'Explanation SLA Configs',
'ordering': ['hospital'],
'indexes': [models.Index(fields=['hospital', 'is_active'], name='complaints__hospita_fe4ec5_idx')],
},
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
from django.conf import settings

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
from django.conf import settings

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
from django.db import migrations, models

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -565,3 +565,8 @@ def send_whatsapp(phone, message, **kwargs):
def send_email(email, subject, message, **kwargs):
"""Send Email notification"""
return NotificationService.send_email(email, subject, message, **kwargs)
def send_notification(recipient, title, message, **kwargs):
"""Send generic notification to a user"""
return NotificationService.send_notification(recipient, title, message, **kwargs)

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import apps.observations.models
import django.db.models.deletion

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid
@ -15,29 +15,6 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='Hospital',
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)),
('name', models.CharField(max_length=200)),
('name_ar', models.CharField(blank=True, max_length=200, verbose_name='Name (Arabic)')),
('code', models.CharField(db_index=True, max_length=50, unique=True)),
('address', models.TextField(blank=True)),
('city', models.CharField(blank=True, max_length=100)),
('phone', models.CharField(blank=True, max_length=20)),
('email', models.EmailField(blank=True, max_length=254)),
('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)),
],
options={
'verbose_name_plural': 'Hospitals',
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Organization',
fields=[
@ -62,6 +39,34 @@ class Migration(migrations.Migration):
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Hospital',
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)),
('name', models.CharField(max_length=200)),
('name_ar', models.CharField(blank=True, max_length=200, verbose_name='Name (Arabic)')),
('code', models.CharField(db_index=True, max_length=50, unique=True)),
('address', models.TextField(blank=True)),
('city', models.CharField(blank=True, max_length=100)),
('phone', models.CharField(blank=True, max_length=20)),
('email', models.EmailField(blank=True, max_length=254)),
('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')),
('ceo', models.ForeignKey(blank=True, help_text='Chief Executive Officer', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hospitals_as_ceo', to=settings.AUTH_USER_MODEL, verbose_name='CEO')),
('cfo', models.ForeignKey(blank=True, help_text='Chief Financial Officer', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hospitals_as_cfo', to=settings.AUTH_USER_MODEL, verbose_name='CFO')),
('coo', models.ForeignKey(blank=True, help_text='Chief Operating Officer', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hospitals_as_coo', to=settings.AUTH_USER_MODEL, verbose_name='COO')),
('medical_director', models.ForeignKey(blank=True, help_text='Medical Director', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hospitals_as_medical_director', to=settings.AUTH_USER_MODEL, verbose_name='Medical Director')),
('organization', models.ForeignKey(blank=True, help_text='Parent organization (null for backward compatibility)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='hospitals', to='organizations.organization')),
],
options={
'verbose_name_plural': 'Hospitals',
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Department',
fields=[
@ -84,11 +89,6 @@ class Migration(migrations.Migration):
'unique_together': {('hospital', 'code')},
},
),
migrations.AddField(
model_name='hospital',
name='organization',
field=models.ForeignKey(blank=True, help_text='Parent organization (null for backward compatibility)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='hospitals', to='organizations.organization'),
),
migrations.CreateModel(
name='Patient',
fields=[
@ -129,10 +129,19 @@ class Migration(migrations.Migration):
('license_number', models.CharField(blank=True, max_length=100, null=True, unique=True)),
('specialization', models.CharField(blank=True, max_length=200)),
('email', models.EmailField(blank=True, max_length=254)),
('phone', models.CharField(blank=True, max_length=20, verbose_name='Phone Number')),
('employee_id', models.CharField(db_index=True, max_length=50, unique=True)),
('name', models.CharField(blank=True, max_length=300, verbose_name='Full Name (Original)')),
('country', models.CharField(blank=True, max_length=100, verbose_name='Country')),
('location', models.CharField(blank=True, max_length=200, verbose_name='Location')),
('gender', models.CharField(blank=True, choices=[('male', 'Male'), ('female', 'Female'), ('other', 'Other')], max_length=10)),
('department_name', models.CharField(blank=True, max_length=200, verbose_name='Department (Original)')),
('section', models.CharField(blank=True, max_length=200, verbose_name='Section')),
('subsection', models.CharField(blank=True, max_length=200, verbose_name='Subsection')),
('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, related_name='staff', to='organizations.department')),
('hospital', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='staff', to='organizations.hospital')),
('report_to', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='direct_reports', to='organizations.staff', verbose_name='Reports To')),
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='staff_profile', to=settings.AUTH_USER_MODEL)),
],
options={

View File

@ -1,41 +0,0 @@
# Generated by Django 6.0.1 on 2026-01-13 13:07
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('organizations', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='hospital',
name='ceo',
field=models.ForeignKey(blank=True, help_text='Chief Executive Officer', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hospitals_as_ceo', to=settings.AUTH_USER_MODEL, verbose_name='CEO'),
),
migrations.AddField(
model_name='hospital',
name='cfo',
field=models.ForeignKey(blank=True, help_text='Chief Financial Officer', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hospitals_as_cfo', to=settings.AUTH_USER_MODEL, verbose_name='CFO'),
),
migrations.AddField(
model_name='hospital',
name='coo',
field=models.ForeignKey(blank=True, help_text='Chief Operating Officer', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hospitals_as_coo', to=settings.AUTH_USER_MODEL, verbose_name='COO'),
),
migrations.AddField(
model_name='hospital',
name='medical_director',
field=models.ForeignKey(blank=True, help_text='Medical Director', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hospitals_as_medical_director', to=settings.AUTH_USER_MODEL, verbose_name='Medical Director'),
),
migrations.AlterField(
model_name='hospital',
name='metadata',
field=models.JSONField(blank=True, default=dict, help_text='Hospital configuration settings'),
),
]

View File

@ -1,44 +0,0 @@
# Generated by Django 6.0.1 on 2026-01-13 13:35
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('organizations', '0002_hospital_ceo_hospital_cfo_hospital_coo_and_more'),
]
operations = [
migrations.AddField(
model_name='staff',
name='country',
field=models.CharField(blank=True, max_length=100, verbose_name='Country'),
),
migrations.AddField(
model_name='staff',
name='department_name',
field=models.CharField(blank=True, max_length=200, verbose_name='Department (Original)'),
),
migrations.AddField(
model_name='staff',
name='gender',
field=models.CharField(blank=True, choices=[('male', 'Male'), ('female', 'Female'), ('other', 'Other')], max_length=10),
),
migrations.AddField(
model_name='staff',
name='report_to',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='direct_reports', to='organizations.staff', verbose_name='Reports To'),
),
migrations.AddField(
model_name='staff',
name='section',
field=models.CharField(blank=True, max_length=200, verbose_name='Section'),
),
migrations.AddField(
model_name='staff',
name='subsection',
field=models.CharField(blank=True, max_length=200, verbose_name='Subsection'),
),
]

View File

@ -1,28 +0,0 @@
# Generated by Django 6.0.1 on 2026-01-13 13:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('organizations', '0003_staff_country_staff_department_name_staff_gender_and_more'),
]
operations = [
migrations.AddField(
model_name='staff',
name='location',
field=models.CharField(blank=True, max_length=200, verbose_name='Location'),
),
migrations.AddField(
model_name='staff',
name='name',
field=models.CharField(blank=True, max_length=300, verbose_name='Full Name (Original)'),
),
migrations.AddField(
model_name='staff',
name='phone',
field=models.CharField(blank=True, max_length=20, verbose_name='Phone Number'),
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
from django.conf import settings

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import apps.references.models
import django.db.models.deletion

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
from django.db import migrations, models

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.core.validators
import django.db.models.deletion

View File

@ -1,4 +1,4 @@
# Generated by Django 6.0.1 on 2026-01-12 09:50
# Generated by Django 6.0.1 on 2026-01-15 12:02
import django.db.models.deletion
import uuid

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
</div>
{% if user.is_px_admin or user.is_hospital_admin %}
<a href="{% url 'organizations:staff_create' %}" class="btn btn-primary">
<i class="fas fa-plus"></i> {% trans "Add New Staff" %}
<i class="bi bi-plus"></i> {% trans "Add New Staff" %}
</a>
{% endif %}
</div>
@ -58,7 +58,7 @@
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-search"></i> {% trans "Search" %}
<i class="bi bi-search"></i> {% trans "Search" %}
</button>
</div>
</form>
@ -122,12 +122,12 @@
<td>
{% if staff_member.user %}
<span class="badge bg-success">
<i class="fas fa-check"></i> {% trans "Yes" %}
<i class="bi bi-check"></i> {% trans "Yes" %}
</span>
<br><small class="text-muted">{{ staff_member.user.username }}</small>
{% else %}
<span class="badge bg-danger">
<i class="fas fa-times"></i> {% trans "No" %}
<i class="bi bi-times"></i> {% trans "No" %}
</span>
{% endif %}
</td>
@ -141,12 +141,12 @@
<td>
<div class="btn-group">
<a href="{% url 'organizations:staff_detail' staff_member.id %}" class="btn btn-sm btn-outline-primary" title="{% trans 'View Details' %}">
<i class="fas fa-eye"></i>
<i class="bi bi-eye"></i>
</a>
{% if user.is_px_admin or user.is_hospital_admin %}
{% if not staff_member.user and staff_member.email %}
<button type="button" class="btn btn-sm btn-outline-success" onclick="createUserAccount('{{ staff_member.id }}', '{{ staff_member.get_full_name }}')" title="{% trans 'Create User Account' %}">
<i class="fas fa-user-plus"></i>
<i class="bi bi-person-fill-add"></i>
</button>
{% endif %}
{% if staff_member.user %}
@ -180,12 +180,12 @@
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% for key, value in filters.items %}{% if value %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
<i class="fas fa-angle-double-left"></i>
<i class="bi bi-chevron-double-left"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% for key, value in filters.items %}{% if value %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
<i class="fas fa-angle-left"></i>
<i class="bi bi-chevron-left"></i>
</a>
</li>
{% endif %}
@ -197,12 +197,12 @@
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% for key, value in filters.items %}{% if value %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
<i class="fas fa-angle-right"></i>
<i class="bi bi-chevron-right"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% for key, value in filters.items %}{% if value %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
<i class="fas fa-angle-double-right"></i>
<i class="bi bi-chevron-double-right"></i>
</a>
</li>
{% endif %}