Some checks failed
System Monitoring / Health Checks (push) Has been cancelled
System Monitoring / Performance Monitoring (push) Has been cancelled
System Monitoring / Database Monitoring (push) Has been cancelled
System Monitoring / Cache Monitoring (push) Has been cancelled
System Monitoring / Log Monitoring (push) Has been cancelled
System Monitoring / Resource Monitoring (push) Has been cancelled
System Monitoring / Uptime Monitoring (push) Has been cancelled
System Monitoring / Backup Monitoring (push) Has been cancelled
System Monitoring / Security Monitoring (push) Has been cancelled
System Monitoring / Monitoring Dashboard (push) Has been cancelled
System Monitoring / Alerting (push) Has been cancelled
Security Scanning / Dependency Scanning (push) Has been cancelled
Security Scanning / Code Security Scanning (push) Has been cancelled
Security Scanning / Secrets Scanning (push) Has been cancelled
Security Scanning / Container Security Scanning (push) Has been cancelled
Security Scanning / Compliance Checking (push) Has been cancelled
Security Scanning / Security Dashboard (push) Has been cancelled
Security Scanning / Security Remediation (push) Has been cancelled
690 lines
25 KiB
Python
690 lines
25 KiB
Python
"""
|
|
Healthcare Appointment Service
|
|
Handles appointment scheduling, management, and healthcare operations
|
|
"""
|
|
from django.db import transaction, models
|
|
from django.utils import timezone
|
|
from django.core.exceptions import ValidationError
|
|
from django.contrib.auth import get_user_model
|
|
from datetime import datetime, timedelta
|
|
|
|
from core.models.tenant import Tenant
|
|
from ..models.patient import Patient
|
|
from ..models.appointment import (
|
|
Appointment, AppointmentResource, AppointmentNote
|
|
)
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class AppointmentService:
|
|
"""
|
|
Service class for managing healthcare appointments
|
|
"""
|
|
|
|
@transaction.atomic
|
|
def create_appointment(self, tenant: Tenant, appointment_data: dict, created_by=None) -> Appointment:
|
|
"""
|
|
Create a new appointment with validation
|
|
|
|
Args:
|
|
tenant: The tenant organization
|
|
appointment_data: Appointment information dictionary
|
|
created_by: User creating the appointment
|
|
|
|
Returns:
|
|
Appointment: Created appointment instance
|
|
|
|
Raises:
|
|
ValidationError: If appointment data is invalid
|
|
"""
|
|
try:
|
|
# Generate unique appointment ID
|
|
appointment_id = self._generate_appointment_id(tenant)
|
|
|
|
# Extract and validate resources and notes
|
|
resources = appointment_data.pop('resources', [])
|
|
notes = appointment_data.pop('notes', [])
|
|
|
|
# Validate appointment time
|
|
self._validate_appointment_time(appointment_data)
|
|
|
|
# Create appointment
|
|
appointment = Appointment.objects.create(
|
|
tenant=tenant,
|
|
appointment_id=appointment_id,
|
|
created_by=created_by,
|
|
**appointment_data
|
|
)
|
|
|
|
# Create resources
|
|
for resource_data in resources:
|
|
AppointmentResource.objects.create(
|
|
appointment=appointment,
|
|
**resource_data
|
|
)
|
|
|
|
# Create notes
|
|
for note_data in notes:
|
|
AppointmentNote.objects.create(
|
|
appointment=appointment,
|
|
created_by=created_by,
|
|
**note_data
|
|
)
|
|
|
|
return appointment
|
|
|
|
except Exception as e:
|
|
raise ValidationError(f"Failed to create appointment: {str(e)}")
|
|
|
|
def update_appointment(self, appointment: Appointment, appointment_data: dict, updated_by=None) -> Appointment:
|
|
"""
|
|
Update appointment information
|
|
|
|
Args:
|
|
appointment: Appointment instance to update
|
|
appointment_data: Updated appointment information
|
|
updated_by: User updating the appointment
|
|
|
|
Returns:
|
|
Appointment: Updated appointment instance
|
|
"""
|
|
try:
|
|
# Validate appointment time if being updated
|
|
if 'appointment_time' in appointment_data or 'appointment_date' in appointment_data:
|
|
temp_data = appointment_data.copy()
|
|
if 'appointment_time' not in temp_data:
|
|
temp_data['appointment_time'] = appointment.appointment_time
|
|
if 'appointment_date' not in temp_data:
|
|
temp_data['appointment_date'] = appointment.appointment_date
|
|
self._validate_appointment_time(temp_data)
|
|
|
|
# Handle resources updates
|
|
if 'resources' in appointment_data:
|
|
self._update_resources(appointment, appointment_data.pop('resources'))
|
|
|
|
# Handle notes updates
|
|
if 'notes' in appointment_data:
|
|
self._update_notes(appointment, appointment_data.pop('notes'), updated_by)
|
|
|
|
# Update appointment fields
|
|
for field, value in appointment_data.items():
|
|
if hasattr(appointment, field):
|
|
setattr(appointment, field, value)
|
|
|
|
appointment.updated_by = updated_by
|
|
appointment.save()
|
|
|
|
return appointment
|
|
|
|
except Exception as e:
|
|
raise ValidationError(f"Failed to update appointment: {str(e)}")
|
|
|
|
def get_appointment_by_id(self, tenant: Tenant, appointment_id: str) -> Appointment:
|
|
"""
|
|
Get appointment by ID within tenant
|
|
|
|
Args:
|
|
tenant: Tenant organization
|
|
appointment_id: Appointment identifier
|
|
|
|
Returns:
|
|
Appointment: Appointment instance
|
|
|
|
Raises:
|
|
Appointment.DoesNotExist: If appointment not found
|
|
"""
|
|
return Appointment.objects.get(
|
|
tenant=tenant,
|
|
appointment_id=appointment_id
|
|
)
|
|
|
|
def get_patient_appointments(self, patient: Patient, start_date=None, end_date=None, status=None):
|
|
"""
|
|
Get appointments for a specific patient
|
|
|
|
Args:
|
|
patient: Patient instance
|
|
start_date: Start date filter
|
|
end_date: End date filter
|
|
status: Status filter
|
|
|
|
Returns:
|
|
QuerySet: Filtered appointments
|
|
"""
|
|
queryset = Appointment.objects.filter(patient=patient)
|
|
|
|
if start_date:
|
|
queryset = queryset.filter(appointment_date__gte=start_date)
|
|
if end_date:
|
|
queryset = queryset.filter(appointment_date__lte=end_date)
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
return queryset.order_by('appointment_date', 'appointment_time')
|
|
|
|
def get_doctor_appointments(self, doctor: User, start_date=None, end_date=None, status=None):
|
|
"""
|
|
Get appointments for a specific doctor
|
|
|
|
Args:
|
|
doctor: Doctor user instance
|
|
start_date: Start date filter
|
|
end_date: End date filter
|
|
status: Status filter
|
|
|
|
Returns:
|
|
QuerySet: Filtered appointments
|
|
"""
|
|
queryset = Appointment.objects.filter(doctor=doctor)
|
|
|
|
if start_date:
|
|
queryset = queryset.filter(appointment_date__gte=start_date)
|
|
if end_date:
|
|
queryset = queryset.filter(appointment_date__lte=end_date)
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
|
|
return queryset.order_by('appointment_date', 'appointment_time')
|
|
|
|
def get_available_time_slots(self, tenant: Tenant, doctor: User, date: datetime.date):
|
|
"""
|
|
Get available time slots for a doctor on a specific date
|
|
|
|
Args:
|
|
tenant: Tenant organization
|
|
doctor: Doctor user instance
|
|
date: Date to check availability
|
|
|
|
Returns:
|
|
list: Available time slots
|
|
"""
|
|
# Get existing appointments for the day
|
|
existing_appointments = Appointment.objects.filter(
|
|
tenant=tenant,
|
|
doctor=doctor,
|
|
appointment_date=date,
|
|
status__in=['scheduled', 'confirmed', 'in_progress']
|
|
)
|
|
|
|
# Define working hours (9 AM to 5 PM)
|
|
working_hours_start = datetime.time(9, 0)
|
|
working_hours_end = datetime.time(17, 0)
|
|
slot_duration = 30 # 30-minute slots
|
|
|
|
available_slots = []
|
|
current_time = datetime.combine(date, working_hours_start)
|
|
end_time = datetime.combine(date, working_hours_end)
|
|
|
|
while current_time < end_time:
|
|
slot_end = current_time + timedelta(minutes=slot_duration)
|
|
|
|
# Check if slot is available
|
|
is_available = True
|
|
for appointment in existing_appointments:
|
|
appt_start = datetime.combine(date, appointment.appointment_time)
|
|
appt_end = appt_start + timedelta(minutes=appointment.duration)
|
|
|
|
if not (slot_end <= appt_start or current_time >= appt_end):
|
|
is_available = False
|
|
break
|
|
|
|
if is_available:
|
|
available_slots.append({
|
|
'start_time': current_time.time(),
|
|
'end_time': slot_end.time(),
|
|
'duration': slot_duration
|
|
})
|
|
|
|
current_time = slot_end
|
|
|
|
return available_slots
|
|
|
|
def check_appointment_conflicts(self, tenant: Tenant, doctor: User, appointment_date: datetime.date,
|
|
appointment_time: datetime.time, duration: int, exclude_appointment=None):
|
|
"""
|
|
Check for appointment conflicts
|
|
|
|
Args:
|
|
tenant: Tenant organization
|
|
doctor: Doctor user instance
|
|
appointment_date: Appointment date
|
|
appointment_time: Appointment time
|
|
duration: Appointment duration in minutes
|
|
exclude_appointment: Appointment to exclude from conflict check
|
|
|
|
Returns:
|
|
list: List of conflicting appointments
|
|
"""
|
|
start_datetime = datetime.combine(appointment_date, appointment_time)
|
|
end_datetime = start_datetime + timedelta(minutes=duration)
|
|
|
|
# Get overlapping appointments
|
|
conflicting_appointments = Appointment.objects.filter(
|
|
tenant=tenant,
|
|
doctor=doctor,
|
|
appointment_date=appointment_date,
|
|
status__in=['scheduled', 'confirmed', 'in_progress']
|
|
)
|
|
|
|
if exclude_appointment:
|
|
conflicting_appointments = conflicting_appointments.exclude(id=exclude_appointment.id)
|
|
|
|
conflicts = []
|
|
for appointment in conflicting_appointments:
|
|
appt_start = datetime.combine(appointment.appointment_date, appointment.appointment_time)
|
|
appt_end = appt_start + timedelta(minutes=appointment.duration)
|
|
|
|
# Check for overlap
|
|
if not (end_datetime <= appt_start or start_datetime >= appt_end):
|
|
conflicts.append(appointment)
|
|
|
|
return conflicts
|
|
|
|
def schedule_appointment(self, tenant: Tenant, patient: Patient, doctor: User,
|
|
appointment_date: datetime.date, appointment_time: datetime.time,
|
|
duration: int = 30, appointment_type: str = 'consultation',
|
|
reason_for_visit: str = '', created_by=None) -> Appointment:
|
|
"""
|
|
Schedule a new appointment with conflict checking
|
|
|
|
Args:
|
|
tenant: Tenant organization
|
|
patient: Patient instance
|
|
doctor: Doctor user instance
|
|
appointment_date: Appointment date
|
|
appointment_time: Appointment time
|
|
duration: Appointment duration in minutes
|
|
appointment_type: Type of appointment
|
|
reason_for_visit: Reason for visit
|
|
created_by: User creating the appointment
|
|
|
|
Returns:
|
|
Appointment: Created appointment
|
|
|
|
Raises:
|
|
ValidationError: If there are scheduling conflicts
|
|
"""
|
|
# Check for conflicts
|
|
conflicts = self.check_appointment_conflicts(
|
|
tenant, doctor, appointment_date, appointment_time, duration
|
|
)
|
|
|
|
if conflicts:
|
|
conflict_times = [f"{conf.appointment_time} ({conf.duration}min)" for conf in conflicts]
|
|
raise ValidationError(
|
|
f"Scheduling conflict with appointments at: {', '.join(conflict_times)}"
|
|
)
|
|
|
|
# Create appointment
|
|
appointment_data = {
|
|
'patient': patient,
|
|
'doctor': doctor,
|
|
'appointment_date': appointment_date,
|
|
'appointment_time': appointment_time,
|
|
'duration': duration,
|
|
'appointment_type': appointment_type,
|
|
'reason_for_visit': reason_for_visit,
|
|
'status': 'scheduled'
|
|
}
|
|
|
|
return self.create_appointment(tenant, appointment_data, created_by)
|
|
|
|
def reschedule_appointment(self, appointment: Appointment, new_date: datetime.date,
|
|
new_time: datetime.time, rescheduled_by=None):
|
|
"""
|
|
Reschedule an existing appointment
|
|
|
|
Args:
|
|
appointment: Appointment to reschedule
|
|
new_date: New appointment date
|
|
new_time: New appointment time
|
|
rescheduled_by: User rescheduling the appointment
|
|
|
|
Returns:
|
|
Appointment: Updated appointment
|
|
|
|
Raises:
|
|
ValidationError: If there are scheduling conflicts
|
|
"""
|
|
# Check for conflicts
|
|
conflicts = self.check_appointment_conflicts(
|
|
appointment.tenant, appointment.doctor, new_date, new_time,
|
|
appointment.duration, exclude_appointment=appointment
|
|
)
|
|
|
|
if conflicts:
|
|
conflict_times = [f"{conf.appointment_time} ({conf.duration}min)" for conf in conflicts]
|
|
raise ValidationError(
|
|
f"Scheduling conflict with appointments at: {', '.join(conflict_times)}"
|
|
)
|
|
|
|
# Create a new appointment record
|
|
old_appointment_data = {
|
|
'patient': appointment.patient,
|
|
'doctor': appointment.doctor,
|
|
'appointment_date': new_date,
|
|
'appointment_time': new_time,
|
|
'duration': appointment.duration,
|
|
'appointment_type': appointment.appointment_type,
|
|
'reason_for_visit': appointment.reason_for_visit,
|
|
'status': 'scheduled',
|
|
'notes': f'Rescheduled from {appointment.appointment_date} at {appointment.appointment_time}',
|
|
'rescheduled_from': appointment
|
|
}
|
|
|
|
new_appointment = self.create_appointment(
|
|
appointment.tenant, old_appointment_data, rescheduled_by
|
|
)
|
|
|
|
# Cancel the old appointment
|
|
appointment.cancel_appointment('Rescheduled', rescheduled_by)
|
|
|
|
return new_appointment
|
|
|
|
def cancel_appointment(self, appointment: Appointment, reason: str, cancelled_by=None):
|
|
"""
|
|
Cancel an appointment
|
|
|
|
Args:
|
|
appointment: Appointment to cancel
|
|
reason: Cancellation reason
|
|
cancelled_by: User cancelling the appointment
|
|
"""
|
|
if not appointment.can_be_cancelled():
|
|
raise ValidationError("This appointment cannot be cancelled")
|
|
|
|
appointment.cancel_appointment(reason, cancelled_by)
|
|
|
|
def send_appointment_reminders(self, tenant: Tenant, hours_before: int = 24):
|
|
"""
|
|
Send appointment reminders
|
|
|
|
Args:
|
|
tenant: Tenant organization
|
|
hours_before: Hours before appointment to send reminder
|
|
|
|
Returns:
|
|
dict: Reminder sending statistics
|
|
"""
|
|
reminder_time = timezone.now() + timedelta(hours=hours_before)
|
|
|
|
# Get appointments that need reminders
|
|
appointments = Appointment.objects.filter(
|
|
tenant=tenant,
|
|
appointment_date=reminder_time.date(),
|
|
status__in=['scheduled', 'confirmed'],
|
|
reminder_sent=False
|
|
).filter(
|
|
models.Q(appointment_time__hour=reminder_time.hour) |
|
|
models.Q(appointment_time__hour=reminder_time.hour - 1)
|
|
)
|
|
|
|
sent_count = 0
|
|
failed_count = 0
|
|
|
|
for appointment in appointments:
|
|
try:
|
|
# Send reminder (integrate with email/SMS service)
|
|
appointment.send_reminder()
|
|
sent_count += 1
|
|
except Exception as e:
|
|
failed_count += 1
|
|
# Log error
|
|
print(f"Failed to send reminder for appointment {appointment.appointment_id}: {e}")
|
|
|
|
return {
|
|
'total_appointments': appointments.count(),
|
|
'sent': sent_count,
|
|
'failed': failed_count
|
|
}
|
|
|
|
def get_appointment_statistics(self, tenant: Tenant, start_date=None, end_date=None):
|
|
"""
|
|
Get appointment statistics for a tenant
|
|
|
|
Args:
|
|
tenant: Tenant organization
|
|
start_date: Start date for statistics
|
|
end_date: End date for statistics
|
|
|
|
Returns:
|
|
dict: Appointment statistics
|
|
"""
|
|
queryset = Appointment.objects.filter(tenant=tenant)
|
|
|
|
if start_date:
|
|
queryset = queryset.filter(appointment_date__gte=start_date)
|
|
if end_date:
|
|
queryset = queryset.filter(appointment_date__lte=end_date)
|
|
|
|
# Basic counts
|
|
total_appointments = queryset.count()
|
|
completed_appointments = queryset.filter(status='completed').count()
|
|
cancelled_appointments = queryset.filter(status='cancelled').count()
|
|
no_show_appointments = queryset.filter(status='no_show').count()
|
|
|
|
# Status distribution
|
|
status_distribution = {}
|
|
for status_choice in Appointment.STATUS_CHOICES:
|
|
status_code = status_choice[0]
|
|
count = queryset.filter(status=status_code).count()
|
|
status_distribution[status_code] = {
|
|
'name': status_choice[1],
|
|
'count': count,
|
|
'percentage': (count / total_appointments * 100) if total_appointments > 0 else 0
|
|
}
|
|
|
|
# Type distribution
|
|
type_distribution = {}
|
|
for type_choice in Appointment.APPOINTMENT_TYPE_CHOICES:
|
|
type_code = type_choice[0]
|
|
count = queryset.filter(appointment_type=type_code).count()
|
|
type_distribution[type_code] = {
|
|
'name': type_choice[1],
|
|
'count': count,
|
|
'percentage': (count / total_appointments * 100) if total_appointments > 0 else 0
|
|
}
|
|
|
|
# Doctor workload
|
|
doctor_workload = {}
|
|
doctors = User.objects.filter(
|
|
id__in=queryset.values_list('doctor', flat=True)
|
|
).distinct()
|
|
|
|
for doctor in doctors:
|
|
doctor_appointments = queryset.filter(doctor=doctor)
|
|
doctor_workload[doctor.id] = {
|
|
'name': f"{doctor.first_name} {doctor.last_name}",
|
|
'total_appointments': doctor_appointments.count(),
|
|
'completed_appointments': doctor_appointments.filter(status='completed').count(),
|
|
'average_duration': doctor_appointments.aggregate(
|
|
avg_duration=models.Avg('duration')
|
|
)['avg_duration'] or 0
|
|
}
|
|
|
|
# Daily trends
|
|
daily_trends = {}
|
|
if start_date and end_date:
|
|
current_date = start_date
|
|
while current_date <= end_date:
|
|
day_count = queryset.filter(appointment_date=current_date).count()
|
|
daily_trends[current_date.strftime('%Y-%m-%d')] = day_count
|
|
current_date += timedelta(days=1)
|
|
|
|
return {
|
|
'total_appointments': total_appointments,
|
|
'completed_appointments': completed_appointments,
|
|
'cancelled_appointments': cancelled_appointments,
|
|
'no_show_appointments': no_show_appointments,
|
|
'completion_rate': (completed_appointments / total_appointments * 100) if total_appointments > 0 else 0,
|
|
'cancellation_rate': (cancelled_appointments / total_appointments * 100) if total_appointments > 0 else 0,
|
|
'no_show_rate': (no_show_appointments / total_appointments * 100) if total_appointments > 0 else 0,
|
|
'status_distribution': status_distribution,
|
|
'type_distribution': type_distribution,
|
|
'doctor_workload': doctor_workload,
|
|
'daily_trends': daily_trends
|
|
}
|
|
|
|
def check_in_patient(self, appointment: Appointment, checked_in_by=None):
|
|
"""
|
|
Check in patient for appointment
|
|
|
|
Args:
|
|
appointment: Appointment instance
|
|
checked_in_by: User checking in the patient
|
|
"""
|
|
appointment.check_in(checked_in_by)
|
|
|
|
def start_appointment(self, appointment: Appointment, started_by=None):
|
|
"""
|
|
Start appointment
|
|
|
|
Args:
|
|
appointment: Appointment instance
|
|
started_by: User starting the appointment
|
|
"""
|
|
appointment.start_appointment(started_by)
|
|
|
|
def complete_appointment(self, appointment: Appointment, completed_by=None,
|
|
diagnosis: str = '', treatment_plan: str = '', notes: str = ''):
|
|
"""
|
|
Complete appointment
|
|
|
|
Args:
|
|
appointment: Appointment instance
|
|
completed_by: User completing the appointment
|
|
diagnosis: Diagnosis information
|
|
treatment_plan: Treatment plan
|
|
notes: Additional notes
|
|
"""
|
|
# Update appointment with completion details
|
|
if diagnosis:
|
|
appointment.diagnosis = diagnosis
|
|
if treatment_plan:
|
|
appointment.treatment_plan = treatment_plan
|
|
if notes:
|
|
appointment.notes = notes
|
|
|
|
appointment.complete_appointment(completed_by)
|
|
|
|
def add_appointment_note(self, appointment: Appointment, note_type: str, title: str,
|
|
content: str, created_by=None, is_confidential=False):
|
|
"""
|
|
Add note to appointment
|
|
|
|
Args:
|
|
appointment: Appointment instance
|
|
note_type: Type of note
|
|
title: Note title
|
|
content: Note content
|
|
created_by: User creating the note
|
|
is_confidential: Whether note is confidential
|
|
"""
|
|
AppointmentNote.objects.create(
|
|
appointment=appointment,
|
|
note_type=note_type,
|
|
title=title,
|
|
content=content,
|
|
is_confidential=is_confidential,
|
|
created_by=created_by
|
|
)
|
|
|
|
def _generate_appointment_id(self, tenant: Tenant) -> str:
|
|
"""
|
|
Generate unique appointment ID for tenant
|
|
|
|
Args:
|
|
tenant: Tenant organization
|
|
|
|
Returns:
|
|
str: Unique appointment ID
|
|
"""
|
|
# Use tenant slug + date + sequence
|
|
tenant_slug = tenant.slug.upper()
|
|
today = timezone.now().strftime('%Y%m%d')
|
|
|
|
# Get today's appointment count
|
|
today_count = Appointment.objects.filter(
|
|
tenant=tenant,
|
|
appointment_date=timezone.now().date()
|
|
).count()
|
|
|
|
sequence = today_count + 1
|
|
|
|
return f"{tenant_slug}-{today}-{sequence:03d}"
|
|
|
|
def _validate_appointment_time(self, appointment_data: dict):
|
|
"""
|
|
Validate appointment time
|
|
|
|
Args:
|
|
appointment_data: Appointment data dictionary
|
|
|
|
Raises:
|
|
ValidationError: If appointment time is invalid
|
|
"""
|
|
appointment_date = appointment_data.get('appointment_date')
|
|
appointment_time = appointment_data.get('appointment_time')
|
|
|
|
if not appointment_date or not appointment_time:
|
|
raise ValidationError("Appointment date and time are required")
|
|
|
|
# Check if appointment is in the past
|
|
appointment_datetime = datetime.combine(appointment_date, appointment_time)
|
|
if appointment_datetime < timezone.now():
|
|
raise ValidationError("Appointment cannot be scheduled in the past")
|
|
|
|
# Check if appointment is outside working hours (9 AM to 6 PM)
|
|
if appointment_time.hour < 9 or appointment_time.hour >= 18:
|
|
raise ValidationError("Appointment must be scheduled between 9 AM and 6 PM")
|
|
|
|
def _update_resources(self, appointment: Appointment, resources_data: list):
|
|
"""
|
|
Update resources for appointment
|
|
|
|
Args:
|
|
appointment: Appointment instance
|
|
resources_data: List of resource data
|
|
"""
|
|
# Remove existing resources not in the update
|
|
existing_ids = [resource.get('id') for resource in resources_data if 'id' in resource]
|
|
appointment.resources.exclude(id__in=existing_ids).delete()
|
|
|
|
# Update or create resources
|
|
for resource_data in resources_data:
|
|
resource_id = resource_data.pop('id', None)
|
|
if resource_id:
|
|
try:
|
|
resource = appointment.resources.get(id=resource_id)
|
|
for field, value in resource_data.items():
|
|
setattr(resource, field, value)
|
|
resource.save()
|
|
except AppointmentResource.DoesNotExist:
|
|
AppointmentResource.objects.create(
|
|
appointment=appointment,
|
|
**resource_data
|
|
)
|
|
else:
|
|
AppointmentResource.objects.create(
|
|
appointment=appointment,
|
|
**resource_data
|
|
)
|
|
|
|
def _update_notes(self, appointment: Appointment, notes_data: list, created_by=None):
|
|
"""
|
|
Update notes for appointment
|
|
|
|
Args:
|
|
appointment: Appointment instance
|
|
notes_data: List of note data
|
|
created_by: User creating notes
|
|
"""
|
|
# Add new notes (notes are not updated, only added)
|
|
for note_data in notes_data:
|
|
if 'id' not in note_data: # Only create new notes
|
|
AppointmentNote.objects.create(
|
|
appointment=appointment,
|
|
created_by=created_by,
|
|
**note_data
|
|
) |