update-po-file
This commit is contained in:
parent
1f9d8a7198
commit
524efbead9
@ -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'), {
|
||||
|
||||
@ -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)),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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={
|
||||
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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'),
|
||||
),
|
||||
]
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
@ -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 %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user