""" MDT (Multi-Disciplinary Team) models. This module handles multidisciplinary collaboration, team notes, and cross-department patient care coordination. """ from django.db import models from django.utils.translation import gettext_lazy as _ from simple_history.models import HistoricalRecords from core.models import ( UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin, ) class MDTNote(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin): """ Multi-Disciplinary Team notes for collaborative patient care. Allows multiple professionals to contribute to a shared clinical note. """ class Status(models.TextChoices): DRAFT = 'DRAFT', _('Draft') PENDING_APPROVAL = 'PENDING_APPROVAL', _('Pending Approval') FINALIZED = 'FINALIZED', _('Finalized') ARCHIVED = 'ARCHIVED', _('Archived') patient = models.ForeignKey( 'core.Patient', on_delete=models.CASCADE, related_name='mdt_notes', verbose_name=_("Patient") ) title = models.CharField( max_length=200, verbose_name=_("Title"), help_text=_("Brief title for this MDT note") ) purpose = models.TextField( verbose_name=_("Purpose"), help_text=_("Purpose of this MDT discussion") ) status = models.CharField( max_length=20, choices=Status.choices, default=Status.DRAFT, verbose_name=_("Status") ) # Contributors initiated_by = models.ForeignKey( 'core.User', on_delete=models.SET_NULL, null=True, related_name='initiated_mdt_notes', verbose_name=_("Initiated By") ) contributors = models.ManyToManyField( 'core.User', through='MDTContribution', related_name='contributed_mdt_notes', verbose_name=_("Contributors") ) # Finalization (requires 2 seniors from different departments) finalized_at = models.DateTimeField( null=True, blank=True, verbose_name=_("Finalized At") ) finalized_by = models.ManyToManyField( 'core.User', through='MDTApproval', related_name='finalized_mdt_notes', verbose_name=_("Finalized By"), help_text=_("Requires at least 2 seniors from different departments") ) # Version Control version = models.PositiveIntegerField( default=1, verbose_name=_("Version") ) # Summary summary = models.TextField( blank=True, verbose_name=_("Summary"), help_text=_("Final summary of MDT discussion and recommendations") ) recommendations = models.TextField( blank=True, verbose_name=_("Recommendations"), help_text=_("Action items and recommendations from MDT") ) history = HistoricalRecords() class Meta: verbose_name = _("MDT Note") verbose_name_plural = _("MDT Notes") ordering = ['-created_at'] indexes = [ models.Index(fields=['patient', 'status']), models.Index(fields=['status', 'created_at']), models.Index(fields=['tenant', 'status']), ] def __str__(self): return f"MDT Note: {self.title} - {self.patient}" @property def is_editable(self): """Check if note can still be edited.""" return self.status in [self.Status.DRAFT, self.Status.PENDING_APPROVAL] @property def can_finalize(self): """Check if note can be finalized.""" # Requires at least 2 approvals from different departments approvals = self.approvals.filter(approved=True) if approvals.count() < 2: return False # Check if approvals are from different departments departments = set() for approval in approvals: if approval.approver.role: departments.add(approval.approver.role) return len(departments) >= 2 def finalize(self): """Finalize the MDT note.""" from django.utils import timezone if not self.can_finalize: raise ValueError("Cannot finalize: requires 2 approvals from different departments") self.status = self.Status.FINALIZED self.finalized_at = timezone.now() self.save() @classmethod def get_pending_for_user(cls, user): """Get MDT notes pending contribution from a specific user.""" return cls.objects.filter( contributors=user, status__in=[cls.Status.DRAFT, cls.Status.PENDING_APPROVAL] ).distinct() class MDTContribution(UUIDPrimaryKeyMixin, TimeStampedMixin): """ Individual contributions to an MDT note. Tracks who contributed what and when. """ mdt_note = models.ForeignKey( MDTNote, on_delete=models.CASCADE, related_name='contributions', verbose_name=_("MDT Note") ) contributor = models.ForeignKey( 'core.User', on_delete=models.CASCADE, related_name='mdt_contributions', verbose_name=_("Contributor") ) clinic = models.ForeignKey( 'core.Clinic', on_delete=models.CASCADE, related_name='mdt_contributions', verbose_name=_("Clinic"), help_text=_("Department/clinic the contributor represents") ) content = models.TextField( verbose_name=_("Content"), help_text=_("Contribution from this professional") ) is_final = models.BooleanField( default=False, verbose_name=_("Is Final"), help_text=_("Whether this contribution is finalized") ) edited_at = models.DateTimeField( auto_now=True, verbose_name=_("Last Edited At") ) # Tagging/Mentions mentioned_users = models.ManyToManyField( 'core.User', blank=True, related_name='mdt_mentions', verbose_name=_("Mentioned Users"), help_text=_("Users tagged in this contribution") ) history = HistoricalRecords() class Meta: verbose_name = _("MDT Contribution") verbose_name_plural = _("MDT Contributions") ordering = ['created_at'] unique_together = [['mdt_note', 'contributor', 'clinic']] indexes = [ models.Index(fields=['mdt_note', 'created_at']), models.Index(fields=['contributor', 'created_at']), ] def __str__(self): return f"{self.contributor.get_full_name()} - {self.clinic.name_en}" class MDTApproval(UUIDPrimaryKeyMixin, TimeStampedMixin): """ Approval records for MDT notes. Requires at least 2 senior therapists from different departments. """ mdt_note = models.ForeignKey( MDTNote, on_delete=models.CASCADE, related_name='approvals', verbose_name=_("MDT Note") ) approver = models.ForeignKey( 'core.User', on_delete=models.CASCADE, related_name='mdt_approvals', verbose_name=_("Approver"), help_text=_("Must be a Senior Therapist") ) clinic = models.ForeignKey( 'core.Clinic', on_delete=models.CASCADE, related_name='mdt_approvals', verbose_name=_("Clinic"), help_text=_("Department the approver represents") ) approved = models.BooleanField( default=False, verbose_name=_("Approved") ) approved_at = models.DateTimeField( null=True, blank=True, verbose_name=_("Approved At") ) comments = models.TextField( blank=True, verbose_name=_("Comments"), help_text=_("Optional comments from the approver") ) class Meta: verbose_name = _("MDT Approval") verbose_name_plural = _("MDT Approvals") ordering = ['approved_at'] unique_together = [['mdt_note', 'approver']] indexes = [ models.Index(fields=['mdt_note', 'approved']), models.Index(fields=['approver', 'approved_at']), ] def __str__(self): status = "Approved" if self.approved else "Pending" return f"{self.approver.get_full_name()} - {status}" def approve(self, comments=""): """Approve the MDT note.""" from django.utils import timezone self.approved = True self.approved_at = timezone.now() self.comments = comments self.save() # Check if MDT note can be finalized if self.mdt_note.can_finalize and self.mdt_note.status != MDTNote.Status.FINALIZED: self.mdt_note.status = MDTNote.Status.PENDING_APPROVAL self.mdt_note.save() class MDTMention(UUIDPrimaryKeyMixin, TimeStampedMixin): """ Tracks when users are mentioned/tagged in MDT contributions. Used for notifications and access control. """ contribution = models.ForeignKey( MDTContribution, on_delete=models.CASCADE, related_name='mentions', verbose_name=_("Contribution") ) mentioned_user = models.ForeignKey( 'core.User', on_delete=models.CASCADE, related_name='received_mdt_mentions', verbose_name=_("Mentioned User") ) notified_at = models.DateTimeField( null=True, blank=True, verbose_name=_("Notified At") ) viewed_at = models.DateTimeField( null=True, blank=True, verbose_name=_("Viewed At") ) class Meta: verbose_name = _("MDT Mention") verbose_name_plural = _("MDT Mentions") ordering = ['-created_at'] unique_together = [['contribution', 'mentioned_user']] indexes = [ models.Index(fields=['mentioned_user', 'viewed_at']), ] def __str__(self): return f"@{self.mentioned_user.username} in {self.contribution.mdt_note.title}" def mark_as_viewed(self): """Mark mention as viewed.""" from django.utils import timezone if not self.viewed_at: self.viewed_at = timezone.now() self.save(update_fields=['viewed_at']) class MDTAttachment(UUIDPrimaryKeyMixin, TimeStampedMixin): """ Attachments for MDT notes (reports, images, documents). """ class FileType(models.TextChoices): REPORT = 'REPORT', _('Report') IMAGE = 'IMAGE', _('Image') DOCUMENT = 'DOCUMENT', _('Document') LAB_RESULT = 'LAB_RESULT', _('Lab Result') ASSESSMENT = 'ASSESSMENT', _('Assessment') OTHER = 'OTHER', _('Other') mdt_note = models.ForeignKey( MDTNote, on_delete=models.CASCADE, related_name='attachments', verbose_name=_("MDT Note") ) file = models.FileField( upload_to='mdt/attachments/%Y/%m/%d/', verbose_name=_("File") ) file_type = models.CharField( max_length=20, choices=FileType.choices, verbose_name=_("File Type") ) description = models.TextField( blank=True, verbose_name=_("Description") ) uploaded_by = models.ForeignKey( 'core.User', on_delete=models.SET_NULL, null=True, related_name='uploaded_mdt_attachments', verbose_name=_("Uploaded By") ) class Meta: verbose_name = _("MDT Attachment") verbose_name_plural = _("MDT Attachments") ordering = ['-created_at'] indexes = [ models.Index(fields=['mdt_note', 'created_at']), ] def __str__(self): return f"{self.file.name} - {self.mdt_note.title}"