Compare commits

..

4 Commits
social ... main

Author SHA1 Message Date
Marwan Alwali
35c076a030 Merge remote-tracking branch 'origin/main' 2026-02-12 21:40:22 +03:00
8fb4fbe3af Merge pull request 'social modified' (#4) from social into main
Reviewed-on: #4
2026-02-12 15:13:50 +03:00
Marwan Alwali
4ceb533fad Merge remote-tracking branch 'origin/main'
# Conflicts:
#	appreciation/admin.py
#	appreciation/models.py
2026-02-12 08:27:48 +03:00
Marwan Alwali
7bddee1647 update 2026-02-02 16:50:42 +03:00
10 changed files with 1401 additions and 1536 deletions

61
api_example.txt Normal file
View File

@ -0,0 +1,61 @@
https://his.alhammadi.med.sa/ClinicalsAPiT/API/FetchPatientVisitTimeStamps?AdmissionID=204541
{
"FetchPatientDataTimeStampList": [
{
"Type": "Patient Demographic details",
"PatientID": "878943",
"AdmissionID": "204541",
"HospitalID": "3",
"HospitalName": "NUZHA-UAT",
"PatientType": "1",
"AdmitDate": "05-Jun-2025 11:06",
"DischargeDate": null,
"RegCode": "ALHH.0000343014",
"SSN": "2180292530",
"PatientName": "AFAF NASSER ALRAZoooOOQ",
"GenderID": "1",
"Gender": "Male",
"FullAge": "46 Year(s)",
"PatientNationality": "Saudi",
"MobileNo": "0550137137",
"DOB": "18-Feb-1979 00:00",
"ConsultantID": "409",
"PrimaryDoctor": "6876-Ahmad Hassan Kakaa ",
"CompanyID": "52799",
"GradeID": "2547",
"CompanyName": "Al Hammadi for Mgmt / Arabian Shield",
"GradeName": "A",
"InsuranceCompanyName": "Arabian Shield Cooperative Insurance Company",
"BillType": "CR",
"IsVIP": "0"
}
],
"FetchPatientDataTimeStampVisitDataList": [
{
"Type": "Consultation",
"BillDate": "05-Jun-2025 11:06"
},
{
"Type": "Doctor Visited",
"BillDate": "05-Jun-2025 11:06"
},
{
"Type": "Clinical Condtion",
"BillDate": "05-Jun-2025 11:12"
},
{
"Type": "ChiefComplaint",
"BillDate": "05-Jun-2025 11:12"
},
{
"Type": "Prescribed Drugs",
"BillDate": "05-Jun-2025 11:12"
}
],
"Code": 200,
"Status": "Success",
"Message": "",
"Message2L": "",
"MobileNo": "",
"ValidateMessage": ""
}

View File

@ -1,204 +0,0 @@
"""
Appreciation admin configuration
"""
from django.contrib import admin
from django.utils.html import format_html
from .models import (
Appreciation,
AppreciationAttachment,
AppreciationCategory,
AppreciationComment,
AppreciationReaction,
AppreciationStatus,
AppreciationType,
)
@admin.register(AppreciationCategory)
class AppreciationCategoryAdmin(admin.ModelAdmin):
"""Admin interface for AppreciationCategory"""
list_display = ['name', 'is_active', 'display_order', 'created_at']
list_filter = ['is_active']
search_fields = ['name', 'description']
list_editable = ['is_active', 'display_order']
class AppreciationReactionInline(admin.TabularInline):
"""Inline admin for AppreciationReaction"""
model = AppreciationReaction
extra = 0
readonly_fields = ['created_at']
class AppreciationCommentInline(admin.TabularInline):
"""Inline admin for AppreciationComment"""
model = AppreciationComment
extra = 0
readonly_fields = ['created_at', 'updated_at']
class AppreciationAttachmentInline(admin.TabularInline):
"""Inline admin for AppreciationAttachment"""
model = AppreciationAttachment
extra = 0
readonly_fields = ['filename', 'file_type', 'file_size', 'created_at']
@admin.register(Appreciation)
class AppreciationAdmin(admin.ModelAdmin):
"""Admin interface for Appreciation"""
list_display = [
'title',
'status_badge',
'type_badge',
'recipient_name',
'hospital',
'submitted_by',
'submitted_at',
]
list_filter = [
'status',
'appreciation_type',
'category',
'hospital',
'is_public',
'share_on_dashboard',
]
search_fields = ['title', 'description', 'recipient_name', 'story']
date_hierarchy = 'created_at'
readonly_fields = [
'created_at',
'updated_at',
'submitted_at',
'acknowledged_at',
'published_at',
]
fieldsets = (
('Basic Information', {
'fields': ('title', 'description', 'story')
}),
('Classification', {
'fields': ('appreciation_type', 'category', 'status')
}),
('Organization', {
'fields': ('hospital', 'department')
}),
('Recipient Information', {
'fields': ('recipient_name', 'recipient_type', 'recipient_id')
}),
('Submitter Information', {
'fields': ('submitted_by', 'submitter_role')
}),
('Acknowledgment', {
'fields': (
'acknowledged_by',
'acknowledged_at',
'acknowledgment_notes'
),
'classes': ('collapse',)
}),
('Timeline', {
'fields': ('submitted_at', 'published_at'),
'classes': ('collapse',)
}),
('Visibility', {
'fields': ('is_public', 'share_on_dashboard', 'share_in_newsletter')
}),
('Additional Details', {
'fields': ('tags', 'impact_score', 'metadata'),
'classes': ('collapse',)
}),
('System', {
'fields': ('created_at', 'updated_at', 'is_deleted'),
'classes': ('collapse',)
}),
)
inlines = [
AppreciationAttachmentInline,
AppreciationReactionInline,
AppreciationCommentInline,
]
def status_badge(self, obj):
colors = {
'draft': 'gray',
'submitted': 'blue',
'acknowledged': 'orange',
'published': 'green',
'archived': 'gray',
}
color = colors.get(obj.status, 'gray')
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 8px; border-radius: 3px;">{}</span>',
color,
obj.get_status_display()
)
status_badge.short_description = 'Status'
def type_badge(self, obj):
colors = {
'staff': 'blue',
'patient': 'green',
'department': 'orange',
'team': 'purple',
'individual': 'teal',
'group': 'pink',
}
color = colors.get(obj.appreciation_type, 'gray')
return format_html(
'<span style="background-color: {}; color: white; padding: 3px 8px; border-radius: 3px;">{}</span>',
color,
obj.get_appreciation_type_display()
)
type_badge.short_description = 'Type'
@admin.register(AppreciationAttachment)
class AppreciationAttachmentAdmin(admin.ModelAdmin):
"""Admin interface for AppreciationAttachment"""
list_display = [
'appreciation',
'filename',
'file_size',
'uploaded_by',
'created_at',
]
list_filter = ['appreciation__hospital', 'created_at']
search_fields = ['filename', 'appreciation__title', 'description']
readonly_fields = ['filename', 'file_type', 'file_size', 'created_at']
@admin.register(AppreciationReaction)
class AppreciationReactionAdmin(admin.ModelAdmin):
"""Admin interface for AppreciationReaction"""
list_display = [
'appreciation',
'user',
'reaction_type',
'created_at',
]
list_filter = ['reaction_type', 'created_at']
search_fields = ['user__email', 'appreciation__title']
readonly_fields = ['created_at']
@admin.register(AppreciationComment)
class AppreciationCommentAdmin(admin.ModelAdmin):
"""Admin interface for AppreciationComment"""
list_display = [
'appreciation',
'comment_short',
'user',
'is_internal',
'created_at',
]
list_filter = ['is_internal', 'created_at']
search_fields = ['comment', 'user__email', 'appreciation__title']
readonly_fields = ['created_at', 'updated_at']
def comment_short(self, obj):
return obj.comment[:50] + '...' if len(obj.comment) > 50 else obj.comment
comment_short.short_description = 'Comment'

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class AppreciationConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'appreciation'

View File

@ -1,409 +0,0 @@
"""
Appreciation models
"""
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.core.models import BaseModel, PriorityChoices
class AppreciationType(models.TextChoices):
"""Types of appreciations"""
STAFF = 'staff', _('Staff Appreciation')
PATIENT = 'patient', _('Patient Appreciation')
DEPARTMENT = 'department', _('Department Appreciation')
TEAM = 'team', _('Team Appreciation')
INDIVIDUAL = 'individual', _('Individual Appreciation')
GROUP = 'group', _('Group Appreciation')
class AppreciationStatus(models.TextChoices):
"""Statuses for appreciations"""
DRAFT = 'draft', _('Draft')
SUBMITTED = 'submitted', _('Submitted')
ACKNOWLEDGED = 'acknowledged', _('Acknowledged')
PUBLISHED = 'published', _('Published')
ARCHIVED = 'archived', _('Archived')
class AppreciationCategory(models.Model):
"""Categories for appreciations"""
name = models.CharField(
max_length=100,
verbose_name=_('Name'),
help_text=_('Category name')
)
description = models.TextField(
blank=True,
verbose_name=_('Description'),
help_text=_('Category description')
)
is_active = models.BooleanField(
default=True,
verbose_name=_('Is Active'),
help_text=_('Whether this category is active')
)
display_order = models.PositiveIntegerField(
default=0,
verbose_name=_('Display Order'),
help_text=_('Order for displaying categories')
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name=_('Created At')
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name=_('Updated At')
)
class Meta:
verbose_name = _('Appreciation Category')
verbose_name_plural = _('Appreciation Categories')
ordering = ['display_order', 'name']
def __str__(self):
return self.name
class Appreciation(BaseModel):
"""Appreciation model for recognizing and rewarding excellence"""
uuid = models.UUIDField(
unique=True,
editable=False,
db_index=True,
verbose_name=_('UUID')
)
# Basic Information
title = models.CharField(
max_length=255,
verbose_name=_('Title'),
help_text=_('Title of the appreciation')
)
description = models.TextField(
verbose_name=_('Description'),
help_text=_('Detailed description of the appreciation')
)
story = models.TextField(
blank=True,
verbose_name=_('Story'),
help_text=_('The story behind this appreciation')
)
# Classification
appreciation_type = models.CharField(
max_length=20,
choices=AppreciationType.choices,
verbose_name=_('Appreciation Type'),
help_text=_('Type of appreciation')
)
category = models.ForeignKey(
AppreciationCategory,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='appreciations',
verbose_name=_('Category'),
help_text=_('Category of appreciation')
)
status = models.CharField(
max_length=20,
choices=AppreciationStatus.choices,
default=AppreciationStatus.DRAFT,
verbose_name=_('Status'),
help_text=_('Status of appreciation')
)
# Organization
hospital = models.ForeignKey(
'organizations.Hospital',
on_delete=models.CASCADE,
related_name='appreciations',
verbose_name=_('Hospital'),
help_text=_('Hospital where this appreciation occurred')
)
department = models.ForeignKey(
'organizations.Department',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='appreciations',
verbose_name=_('Department'),
help_text=_('Department where this appreciation occurred')
)
# Recipient Information
recipient_name = models.CharField(
max_length=255,
verbose_name=_('Recipient Name'),
help_text=_('Name of the person or team being appreciated')
)
recipient_type = models.CharField(
max_length=50,
verbose_name=_('Recipient Type'),
help_text=_('Type of recipient (e.g., Staff, Patient, Department)')
)
recipient_id = models.CharField(
max_length=100,
blank=True,
verbose_name=_('Recipient ID'),
help_text=_('ID of the recipient in the system')
)
# Submitter Information
submitted_by = models.ForeignKey(
'accounts.User',
on_delete=models.CASCADE,
related_name='submitted_appreciations',
verbose_name=_('Submitted By'),
help_text=_('User who submitted this appreciation')
)
submitter_role = models.CharField(
max_length=100,
blank=True,
verbose_name=_('Submitter Role'),
help_text=_('Role of the submitter')
)
# Acknowledgment
acknowledged_by = models.ForeignKey(
'accounts.User',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='acknowledged_appreciations',
verbose_name=_('Acknowledged By'),
help_text=_('User who acknowledged this appreciation')
)
acknowledged_at = models.DateTimeField(
null=True,
blank=True,
verbose_name=_('Acknowledged At'),
help_text=_('When this appreciation was acknowledged')
)
acknowledgment_notes = models.TextField(
blank=True,
verbose_name=_('Acknowledgment Notes'),
help_text=_('Notes added during acknowledgment')
)
# Timeline
submitted_at = models.DateTimeField(
null=True,
blank=True,
verbose_name=_('Submitted At'),
help_text=_('When this appreciation was submitted')
)
published_at = models.DateTimeField(
null=True,
blank=True,
verbose_name=_('Published At'),
help_text=_('When this appreciation was published')
)
# Visibility and Sharing
is_public = models.BooleanField(
default=False,
verbose_name=_('Is Public'),
help_text=_('Whether this appreciation is publicly visible')
)
share_on_dashboard = models.BooleanField(
default=True,
verbose_name=_('Share on Dashboard'),
help_text=_('Whether to display on the dashboard')
)
share_in_newsletter = models.BooleanField(
default=False,
verbose_name=_('Share in Newsletter'),
help_text=_('Whether to include in newsletter')
)
# Additional Details
tags = models.JSONField(
default=list,
blank=True,
verbose_name=_('Tags'),
help_text=_('Tags for categorization and search')
)
impact_score = models.PositiveIntegerField(
default=0,
verbose_name=_('Impact Score'),
help_text=_('Score indicating the impact of this appreciation')
)
metadata = models.JSONField(
default=dict,
blank=True,
verbose_name=_('Metadata'),
help_text=_('Additional metadata as key-value pairs'
)
class Meta:
verbose_name = _('Appreciation')
verbose_name_plural = _('Appreciations')
ordering = ['-created_at']
indexes = [
models.Index(fields=['status']),
models.Index(fields=['appreciation_type']),
models.Index(fields=['hospital']),
models.Index(fields=['created_at']),
]
def __str__(self):
return f"{self.title} - {self.recipient_name}"
def save(self, *args, **kwargs):
# Auto-set submitted_at when status changes to submitted
if self.status == AppreciationStatus.SUBMITTED and not self.submitted_at:
from django.utils import timezone
self.submitted_at = timezone.now()
# Auto-set published_at when status changes to published
if self.status == AppreciationStatus.PUBLISHED and not self.published_at:
from django.utils import timezone
self.published_at = timezone.now()
super().save(*args, **kwargs)
class AppreciationAttachment(models.Model):
"""Attachments for appreciations"""
appreciation = models.ForeignKey(
Appreciation,
on_delete=models.CASCADE,
related_name='attachments',
verbose_name=_('Appreciation'),
help_text=_('Associated appreciation')
)
file = models.FileField(
upload_to='appreciations/attachments/%Y/%m/',
verbose_name=_('File'),
help_text=_('Attachment file')
)
filename = models.CharField(
max_length=255,
verbose_name=_('Filename'),
help_text=_('Original filename')
)
file_type = models.CharField(
max_length=100,
blank=True,
verbose_name=_('File Type'),
help_text=_('MIME type of file')
)
file_size = models.PositiveIntegerField(
verbose_name=_('File Size'),
help_text=_('Size of file in bytes')
)
description = models.TextField(
blank=True,
verbose_name=_('Description'),
help_text=_('Description of the attachment')
)
uploaded_by = models.ForeignKey(
'accounts.User',
on_delete=models.CASCADE,
related_name='appreciation_uploads',
verbose_name=_('Uploaded By'),
help_text=_('User who uploaded this attachment')
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name=_('Created At')
)
class Meta:
verbose_name = _('Appreciation Attachment')
verbose_name_plural = _('Appreciation Attachments')
ordering = ['-created_at']
def __str__(self):
return f"{self.filename} - {self.appreciation.title}"
class AppreciationReaction(models.Model):
"""Reactions to appreciations (likes, emojis, etc.)"""
appreciation = models.ForeignKey(
Appreciation,
on_delete=models.CASCADE,
related_name='reactions',
verbose_name=_('Appreciation'),
help_text=_('Associated appreciation')
)
user = models.ForeignKey(
'accounts.User',
on_delete=models.CASCADE,
related_name='appreciation_reactions',
verbose_name=_('User'),
help_text=_('User who reacted')
)
reaction_type = models.CharField(
max_length=50,
verbose_name=_('Reaction Type'),
help_text=_('Type of reaction (e.g., like, heart, star)'
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name=_('Created At')
)
class Meta:
verbose_name = _('Appreciation Reaction')
verbose_name_plural = _('Appreciation Reactions')
unique_together = ['appreciation', 'user']
ordering = ['-created_at']
def __str__(self):
return f"{self.user.email} - {self.reaction_type}"
class AppreciationComment(models.Model):
"""Comments on appreciations"""
appreciation = models.ForeignKey(
Appreciation,
on_delete=models.CASCADE,
related_name='comments',
verbose_name=_('Appreciation'),
help_text=_('Associated appreciation')
)
user = models.ForeignKey(
'accounts.User',
on_delete=models.CASCADE,
related_name='appreciation_comments',
verbose_name=_('User'),
help_text=_('User who commented')
)
comment = models.TextField(
verbose_name=_('Comment'),
help_text=_('Comment text')
)
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='replies',
verbose_name=_('Parent Comment'),
help_text=_('Parent comment for nested replies')
)
is_internal = models.BooleanField(
default=False,
verbose_name=_('Is Internal'),
help_text=_('Whether this is an internal comment')
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name=_('Created At')
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name=_('Updated At')
)
class Meta:
verbose_name = _('Appreciation Comment')
verbose_name_plural = _('Appreciation Comments')
ordering = ['-created_at']
def __str__(self):
return f"{self.user.email}: {self.comment[:50]}"

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@ -69,7 +69,7 @@ SAUDI_ORGANIZATIONS = [
] ]
SAUDI_HOSPITALS = [ SAUDI_HOSPITALS = [
{'name': 'Alhammadi Hospital', 'name_ar': 'مستشفى الحمادي', 'city': 'Riyadh', 'code': 'HH'}, {'name': 'Nuzha', 'name_ar': 'النزهة', 'city': 'Riyadh', 'code': 'NZ'},
# {'name': 'King Faisal Specialist Hospital', 'name_ar': 'مستشفى الملك فيصل التخصصي', 'city': 'Riyadh', 'code': 'KFSH'}, # {'name': 'King Faisal Specialist Hospital', 'name_ar': 'مستشفى الملك فيصل التخصصي', 'city': 'Riyadh', 'code': 'KFSH'},
# {'name': 'King Abdulaziz Medical City', 'name_ar': 'مدينة الملك عبدالعزيز الطبية', 'city': 'Riyadh', 'code': 'KAMC'}, # {'name': 'King Abdulaziz Medical City', 'name_ar': 'مدينة الملك عبدالعزيز الطبية', 'city': 'Riyadh', 'code': 'KAMC'},
# {'name': 'King Khalid University Hospital', 'name_ar': 'مستشفى الملك خالد الجامعي', 'city': 'Riyadh', 'code': 'KKUH'}, # {'name': 'King Khalid University Hospital', 'name_ar': 'مستشفى الملك خالد الجامعي', 'city': 'Riyadh', 'code': 'KKUH'},

File diff suppressed because it is too large Load Diff