update
This commit is contained in:
parent
2b4c58aa34
commit
4d06ca4b5e
20
.idea/dataSources.xml
generated
Normal file
20
.idea/dataSources.xml
generated
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="db" uuid="0a1c7514-5bfb-4128-82a6-2adb0a4546e2">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db.sqlite3</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
<libraries>
|
||||
<library>
|
||||
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
|
||||
</library>
|
||||
<library>
|
||||
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
|
||||
</library>
|
||||
</libraries>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
15461
AR-DRG Index Appendix A
Normal file
15461
AR-DRG Index Appendix A
Normal file
File diff suppressed because it is too large
Load Diff
14304
AR-DRG Index Appendix B
Normal file
14304
AR-DRG Index Appendix B
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
AR-DRG Version 9.0 Definitions Manual Errata 2 - March 2017.pdf
Normal file
BIN
AR-DRG Version 9.0 Definitions Manual Errata 2 - March 2017.pdf
Normal file
Binary file not shown.
BIN
Archive.zip
Normal file
BIN
Archive.zip
Normal file
Binary file not shown.
Binary file not shown.
BIN
Map ADA Dental Code to SBS V2.xlsx
Normal file
BIN
Map ADA Dental Code to SBS V2.xlsx
Normal file
Binary file not shown.
BIN
Mapping Template.xlsx
Normal file
BIN
Mapping Template.xlsx
Normal file
Binary file not shown.
BIN
SBS V2 Alphabetic Index.pdf
Normal file
BIN
SBS V2 Alphabetic Index.pdf
Normal file
Binary file not shown.
BIN
SBS V2 Summary of Changes.pdf
Normal file
BIN
SBS V2 Summary of Changes.pdf
Normal file
Binary file not shown.
BIN
SBSCS_Saudi Billing System Coding Standards_V2.0.pdf
Normal file
BIN
SBSCS_Saudi Billing System Coding Standards_V2.0.pdf
Normal file
Binary file not shown.
BIN
SBS_V2_Code_list.xlsx
Normal file
BIN
SBS_V2_Code_list.xlsx
Normal file
Binary file not shown.
BIN
SBS_V2_Code_list_URLs.xlsx
Normal file
BIN
SBS_V2_Code_list_URLs.xlsx
Normal file
Binary file not shown.
BIN
Saudi-Health-Data-Dictionary-v2.pdf
Normal file
BIN
Saudi-Health-Data-Dictionary-v2.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
@ -386,6 +387,21 @@ class Migration(migrations.Migration):
|
||||
verbose_name="superuser status",
|
||||
),
|
||||
),
|
||||
(
|
||||
"username",
|
||||
models.CharField(
|
||||
error_messages={
|
||||
"unique": "A user with that username already exists."
|
||||
},
|
||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||
max_length=150,
|
||||
unique=True,
|
||||
validators=[
|
||||
django.contrib.auth.validators.UnicodeUsernameValidator()
|
||||
],
|
||||
verbose_name="username",
|
||||
),
|
||||
),
|
||||
(
|
||||
"first_name",
|
||||
models.CharField(
|
||||
@ -424,67 +440,15 @@ class Migration(migrations.Migration):
|
||||
"user_id",
|
||||
models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
|
||||
),
|
||||
(
|
||||
"username",
|
||||
models.CharField(
|
||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||
max_length=150,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
("email", models.EmailField(blank=True, max_length=254, null=True)),
|
||||
(
|
||||
"force_password_change",
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text="User must change password on next login",
|
||||
),
|
||||
),
|
||||
(
|
||||
"password_expires_at",
|
||||
models.DateTimeField(
|
||||
blank=True, help_text="Password expiration date", null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"failed_login_attempts",
|
||||
models.PositiveIntegerField(
|
||||
default=0, help_text="Number of failed login attempts"
|
||||
),
|
||||
),
|
||||
(
|
||||
"locked_until",
|
||||
models.DateTimeField(
|
||||
blank=True,
|
||||
help_text="Account locked until this time",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"two_factor_enabled",
|
||||
models.BooleanField(
|
||||
default=False, help_text="Two-factor authentication enabled"
|
||||
),
|
||||
),
|
||||
(
|
||||
"max_concurrent_sessions",
|
||||
models.PositiveIntegerField(
|
||||
default=3, help_text="Maximum concurrent sessions allowed"
|
||||
),
|
||||
),
|
||||
(
|
||||
"session_timeout_minutes",
|
||||
models.PositiveIntegerField(
|
||||
default=30, help_text="Session timeout in minutes"
|
||||
),
|
||||
),
|
||||
(
|
||||
"last_password_change",
|
||||
models.DateTimeField(
|
||||
default=django.utils.timezone.now,
|
||||
help_text="Last password change date",
|
||||
),
|
||||
),
|
||||
("email", models.EmailField(max_length=254, unique=True)),
|
||||
("force_password_change", models.BooleanField(default=False)),
|
||||
("password_expires_at", models.DateTimeField(blank=True, null=True)),
|
||||
("failed_login_attempts", models.PositiveIntegerField(default=0)),
|
||||
("locked_until", models.DateTimeField(blank=True, null=True)),
|
||||
("two_factor_enabled", models.BooleanField(default=False)),
|
||||
("max_concurrent_sessions", models.PositiveIntegerField(default=3)),
|
||||
("session_timeout_minutes", models.PositiveIntegerField(default=30)),
|
||||
("last_password_change", models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
@ -20,7 +20,6 @@ class Migration(migrations.Migration):
|
||||
model_name="user",
|
||||
name="tenant",
|
||||
field=models.ForeignKey(
|
||||
help_text="Organization tenant",
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="users",
|
||||
to="core.tenant",
|
||||
@ -86,12 +85,6 @@ class Migration(migrations.Migration):
|
||||
fields=["tenant", "username"], name="accounts_us_tenant__d92906_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="user",
|
||||
index=models.Index(
|
||||
fields=["tenant", "user_id"], name="accounts_us_tenant__bd3758_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="user",
|
||||
index=models.Index(
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("accounts", "0002_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="is_active",
|
||||
field=models.BooleanField(default=True, help_text="User account is active"),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -359,71 +359,17 @@ from django.conf import settings
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
"""
|
||||
Minimal auth user for a multi-tenant app:
|
||||
- Authentication core (from AbstractUser)
|
||||
- Tenant link
|
||||
- Security/session controls (lockout, password expiry, 2FA flag, session caps)
|
||||
Everything else lives on hr.Employee.
|
||||
"""
|
||||
|
||||
# Stable internal UUID
|
||||
user_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
||||
|
||||
# Tenant (PROTECT = safer than cascading deletion)
|
||||
tenant = models.ForeignKey(
|
||||
'core.Tenant',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='users',
|
||||
help_text='Organization tenant',
|
||||
)
|
||||
|
||||
username = models.CharField(
|
||||
max_length=150,
|
||||
unique=True,
|
||||
help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'
|
||||
)
|
||||
email = models.EmailField(blank=True, null=True)
|
||||
|
||||
# --- Security & session controls kept on User (auth-level concerns) ---
|
||||
force_password_change = models.BooleanField(
|
||||
default=False,
|
||||
help_text='User must change password on next login'
|
||||
)
|
||||
password_expires_at = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Password expiration date'
|
||||
)
|
||||
failed_login_attempts = models.PositiveIntegerField(
|
||||
default=0,
|
||||
help_text='Number of failed login attempts'
|
||||
)
|
||||
locked_until = models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Account locked until this time'
|
||||
)
|
||||
two_factor_enabled = models.BooleanField(
|
||||
default=False,
|
||||
help_text='Two-factor authentication enabled'
|
||||
)
|
||||
max_concurrent_sessions = models.PositiveIntegerField(
|
||||
default=3,
|
||||
help_text='Maximum concurrent sessions allowed'
|
||||
)
|
||||
session_timeout_minutes = models.PositiveIntegerField(
|
||||
default=30,
|
||||
help_text='Session timeout in minutes'
|
||||
)
|
||||
last_password_change = models.DateTimeField(
|
||||
default=timezone.now,
|
||||
help_text='Last password change date'
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
default=True,
|
||||
help_text='User account is active'
|
||||
)
|
||||
user_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
|
||||
email = models.EmailField(unique=True)
|
||||
tenant = models.ForeignKey('core.Tenant', on_delete=models.PROTECT, related_name='users')
|
||||
force_password_change = models.BooleanField(default=False)
|
||||
password_expires_at = models.DateTimeField(blank=True, null=True)
|
||||
failed_login_attempts = models.PositiveIntegerField(default=0)
|
||||
locked_until = models.DateTimeField(blank=True, null=True)
|
||||
two_factor_enabled = models.BooleanField(default=False)
|
||||
max_concurrent_sessions = models.PositiveIntegerField(default=3)
|
||||
session_timeout_minutes = models.PositiveIntegerField(default=30)
|
||||
last_password_change = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'accounts_user'
|
||||
@ -431,15 +377,15 @@ class User(AbstractUser):
|
||||
indexes = [
|
||||
models.Index(fields=['tenant', 'email']),
|
||||
models.Index(fields=['tenant', 'username']),
|
||||
models.Index(fields=['tenant', 'user_id']),
|
||||
models.Index(fields=['tenant', 'is_active']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
full = super().get_full_name().strip()
|
||||
return f"{full or self.username} (tenant={self.tenant_id})"
|
||||
return f"{full or self.username}"
|
||||
|
||||
# ---- Security helpers ----
|
||||
|
||||
@property
|
||||
def is_account_locked(self) -> bool:
|
||||
return bool(self.locked_until and timezone.now() < self.locked_until)
|
||||
@ -477,13 +423,22 @@ class User(AbstractUser):
|
||||
self.failed_login_attempts = 0
|
||||
self.save(update_fields=['failed_login_attempts'])
|
||||
|
||||
def set_password(self, raw_password):
|
||||
super().set_password(raw_password)
|
||||
self.last_password_change = timezone.now()
|
||||
|
||||
|
||||
class TwoFactorDevice(models.Model):
|
||||
"""
|
||||
Two-factor authentication devices for users.
|
||||
"""
|
||||
|
||||
# User relationship
|
||||
class DeviceType(models.TextChoices):
|
||||
TOTP = 'TOTP', 'Time-based OTP (Authenticator App)'
|
||||
SMS = 'SMS', 'SMS'
|
||||
EMAIL = 'EMAIL', 'Email'
|
||||
HARDWARE = 'HARDWARE', 'Hardware Token'
|
||||
BACKUP = 'BACKUP', 'Backup Codes'
|
||||
|
||||
|
||||
|
||||
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
@ -501,16 +456,7 @@ class TwoFactorDevice(models.Model):
|
||||
max_length=100,
|
||||
help_text='Device name'
|
||||
)
|
||||
device_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('TOTP', 'Time-based OTP (Authenticator App)'),
|
||||
('SMS', 'SMS'),
|
||||
('EMAIL', 'Email'),
|
||||
('HARDWARE', 'Hardware Token'),
|
||||
('BACKUP', 'Backup Codes'),
|
||||
]
|
||||
)
|
||||
device_type = models.CharField(max_length=20,choices=DeviceType.choices)
|
||||
|
||||
# Device Configuration
|
||||
secret_key = models.CharField(
|
||||
@ -575,7 +521,17 @@ class SocialAccount(models.Model):
|
||||
"""
|
||||
Social authentication accounts linked to users.
|
||||
"""
|
||||
|
||||
class Provider(models.TextChoices):
|
||||
GOOGLE = 'GOOGLE', 'Google'
|
||||
MICROSOFT = 'MICROSOFT', 'Microsoft'
|
||||
APPLE = 'APPLE', 'Apple'
|
||||
FACEBOOK = 'FACEBOOK', 'Facebook'
|
||||
LINKEDIN = 'LINKEDIN', 'LinkedIn'
|
||||
GITHUB = 'GITHUB', 'GitHub'
|
||||
OKTA = 'OKTA', 'Okta'
|
||||
SAML = 'SAML', 'SAML'
|
||||
LDAP = 'LDAP', 'LDAP'
|
||||
|
||||
# User relationship
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
@ -584,20 +540,7 @@ class SocialAccount(models.Model):
|
||||
)
|
||||
|
||||
# Provider Information
|
||||
provider = models.CharField(
|
||||
max_length=50,
|
||||
choices=[
|
||||
('GOOGLE', 'Google'),
|
||||
('MICROSOFT', 'Microsoft'),
|
||||
('APPLE', 'Apple'),
|
||||
('FACEBOOK', 'Facebook'),
|
||||
('LINKEDIN', 'LinkedIn'),
|
||||
('GITHUB', 'GitHub'),
|
||||
('OKTA', 'Okta'),
|
||||
('SAML', 'SAML'),
|
||||
('LDAP', 'LDAP'),
|
||||
]
|
||||
)
|
||||
provider = models.CharField(max_length=50,choices=Provider.choices)
|
||||
provider_id = models.CharField(
|
||||
max_length=200,
|
||||
help_text='Provider user ID'
|
||||
@ -673,7 +616,21 @@ class UserSession(models.Model):
|
||||
"""
|
||||
User session tracking for security and audit purposes.
|
||||
"""
|
||||
|
||||
class DeviceType(models.TextChoices):
|
||||
DESKTOP = 'DESKTOP', 'Desktop'
|
||||
MOBILE = 'MOBILE', 'Mobile'
|
||||
TABLET = 'TABLET', 'Tablet'
|
||||
UNKNOWN = 'UNKNOWN', 'Unknown'
|
||||
|
||||
class LoginMethod(models.TextChoices):
|
||||
PASSWORD = 'PASSWORD', 'Password'
|
||||
TWO_FACTOR = 'TWO_FACTOR', 'Two Factor'
|
||||
SOCIAL = 'SOCIAL', 'Social Login'
|
||||
SSO = 'SSO', 'Single Sign-On'
|
||||
API_KEY = 'API_KEY', 'API Key'
|
||||
|
||||
|
||||
|
||||
# User relationship
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
@ -703,12 +660,7 @@ class UserSession(models.Model):
|
||||
)
|
||||
device_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('DESKTOP', 'Desktop'),
|
||||
('MOBILE', 'Mobile'),
|
||||
('TABLET', 'Tablet'),
|
||||
('UNKNOWN', 'Unknown'),
|
||||
],
|
||||
choices=DeviceType.choices,
|
||||
default='UNKNOWN'
|
||||
)
|
||||
browser = models.CharField(
|
||||
@ -751,14 +703,8 @@ class UserSession(models.Model):
|
||||
)
|
||||
login_method = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('PASSWORD', 'Password'),
|
||||
('TWO_FACTOR', 'Two Factor'),
|
||||
('SOCIAL', 'Social Login'),
|
||||
('SSO', 'Single Sign-On'),
|
||||
('API_KEY', 'API Key'),
|
||||
],
|
||||
default='PASSWORD'
|
||||
choices=LoginMethod.choices,
|
||||
default=LoginMethod.PASSWORD
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
|
||||
@ -8,34 +8,28 @@ from hr.models import Employee
|
||||
from .models import User
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def create_employee_for_user(sender, instance: User, created: bool, **kwargs):
|
||||
"""
|
||||
Auto-create an Employee profile when a User is created.
|
||||
Idempotent, tenant-aligned, and makes a unique employee_number per tenant.
|
||||
"""
|
||||
@receiver(post_save, sender=User, dispatch_uid="create_employee_on_user_create_simple")
|
||||
def create_employee_on_user_create(sender, instance: User, created: bool, **kwargs):
|
||||
if not created:
|
||||
return
|
||||
|
||||
def _make_employee():
|
||||
# Generate a readable employee_number from username/email, unique within tenant
|
||||
base = (instance.username or (instance.email or 'user')).split('@')[0]
|
||||
candidate = slugify(base)[:16] or 'emp'
|
||||
suffix = 1
|
||||
emp_no = candidate
|
||||
while Employee.objects.filter(tenant=instance.tenant, employee_number=emp_no).exists():
|
||||
suffix += 1
|
||||
emp_no = f"{candidate}-{suffix}"
|
||||
def _make():
|
||||
tenant = getattr(instance, "tenant", None)
|
||||
email = (instance.email or "").strip()
|
||||
|
||||
# If already linked OR same email exists in this tenant, skip
|
||||
if Employee.objects.filter(user=instance).exists():
|
||||
return
|
||||
if email and Employee.objects.filter(tenant=tenant, email__iexact=email).exists():
|
||||
return
|
||||
|
||||
# Create with basic info mirrored from User
|
||||
Employee.objects.create(
|
||||
tenant=instance.tenant,
|
||||
tenant=tenant,
|
||||
user=instance,
|
||||
employee_number=emp_no,
|
||||
first_name=instance.first_name or '',
|
||||
last_name=instance.last_name or '',
|
||||
email=instance.email
|
||||
first_name=instance.first_name or "",
|
||||
last_name=instance.last_name or "",
|
||||
email=email,
|
||||
# employee_id will be auto-generated in Employee.save()
|
||||
)
|
||||
|
||||
# Defer until after outer transaction commits (avoids race conditions in tests/views)
|
||||
transaction.on_commit(_make_employee)
|
||||
transaction.on_commit(_make)
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
@ -700,6 +700,7 @@ class Migration(migrations.Migration):
|
||||
blank=True,
|
||||
default=list,
|
||||
help_text="Acceptable days of week (0=Monday, 6=Sunday)",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-16 12:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("appointments", "0002_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="waitinglist",
|
||||
name="acceptable_days",
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
default=list,
|
||||
help_text="Acceptable days of week (0=Monday, 6=Sunday)",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -16,65 +16,66 @@ class AppointmentRequest(models.Model):
|
||||
"""
|
||||
Appointment request model for scheduling patient appointments.
|
||||
"""
|
||||
APPOINTMENT_TYPE_CHOICES = [
|
||||
('CONSULTATION', 'Consultation'),
|
||||
('FOLLOW_UP', 'Follow-up'),
|
||||
('PROCEDURE', 'Procedure'),
|
||||
('SURGERY', 'Surgery'),
|
||||
('DIAGNOSTIC', 'Diagnostic'),
|
||||
('THERAPY', 'Therapy'),
|
||||
('VACCINATION', 'Vaccination'),
|
||||
('SCREENING', 'Screening'),
|
||||
('EMERGENCY', 'Emergency'),
|
||||
('TELEMEDICINE', 'Telemedicine'),
|
||||
('OTHER', 'Other'),
|
||||
]
|
||||
SPECIALTY_CHOICES = [
|
||||
('FAMILY_MEDICINE', 'Family Medicine'),
|
||||
('INTERNAL_MEDICINE', 'Internal Medicine'),
|
||||
('PEDIATRICS', 'Pediatrics'),
|
||||
('CARDIOLOGY', 'Cardiology'),
|
||||
('DERMATOLOGY', 'Dermatology'),
|
||||
('ENDOCRINOLOGY', 'Endocrinology'),
|
||||
('GASTROENTEROLOGY', 'Gastroenterology'),
|
||||
('NEUROLOGY', 'Neurology'),
|
||||
('ONCOLOGY', 'Oncology'),
|
||||
('ORTHOPEDICS', 'Orthopedics'),
|
||||
('PSYCHIATRY', 'Psychiatry'),
|
||||
('RADIOLOGY', 'Radiology'),
|
||||
('SURGERY', 'Surgery'),
|
||||
('UROLOGY', 'Urology'),
|
||||
('GYNECOLOGY', 'Gynecology'),
|
||||
('OPHTHALMOLOGY', 'Ophthalmology'),
|
||||
('ENT', 'Ear, Nose & Throat'),
|
||||
('EMERGENCY', 'Emergency Medicine'),
|
||||
('OTHER', 'Other'),
|
||||
]
|
||||
PRIORITY_CHOICES=[
|
||||
('ROUTINE', 'Routine'),
|
||||
('URGENT', 'Urgent'),
|
||||
('STAT', 'STAT'),
|
||||
('EMERGENCY', 'Emergency'),
|
||||
]
|
||||
STATUS_CHOICES=[
|
||||
('PENDING', 'Pending'),
|
||||
('SCHEDULED', 'Scheduled'),
|
||||
('CONFIRMED', 'Confirmed'),
|
||||
('CHECKED_IN', 'Checked In'),
|
||||
('IN_PROGRESS', 'In Progress'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('NO_SHOW', 'No Show'),
|
||||
('RESCHEDULED', 'Rescheduled'),
|
||||
]
|
||||
TELEMEDICINE_PLATFORM_CHOICES=[
|
||||
('ZOOM', 'Zoom'),
|
||||
('TEAMS', 'Microsoft Teams'),
|
||||
('WEBEX', 'Cisco Webex'),
|
||||
('DOXY', 'Doxy.me'),
|
||||
('CUSTOM', 'Custom Platform'),
|
||||
('OTHER', 'Other'),
|
||||
]
|
||||
|
||||
class AppointmentType(models.TextChoices):
|
||||
CONSULTATION = 'CONSULTATION', 'Consultation'
|
||||
FOLLOW_UP = 'FOLLOW_UP', 'Follow-up'
|
||||
PROCEDURE = 'PROCEDURE', 'Procedure'
|
||||
SURGERY = 'SURGERY', 'Surgery'
|
||||
DIAGNOSTIC = 'DIAGNOSTIC', 'Diagnostic'
|
||||
THERAPY = 'THERAPY', 'Therapy'
|
||||
VACCINATION = 'VACCINATION', 'Vaccination'
|
||||
SCREENING = 'SCREENING', 'Screening'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||
TELEMEDICINE = 'TELEMEDICINE', 'Telemedicine'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class Specialty(models.TextChoices):
|
||||
FAMILY_MEDICINE = 'FAMILY_MEDICINE', 'Family Medicine'
|
||||
INTERNAL_MEDICINE = 'INTERNAL_MEDICINE', 'Internal Medicine'
|
||||
PEDIATRICS = 'PEDIATRICS', 'Pediatrics'
|
||||
CARDIOLOGY = 'CARDIOLOGY', 'Cardiology'
|
||||
DERMATOLOGY = 'DERMATOLOGY', 'Dermatology'
|
||||
ENDOCRINOLOGY = 'ENDOCRINOLOGY', 'Endocrinology'
|
||||
GASTROENTEROLOGY = 'GASTROENTEROLOGY', 'Gastroenterology'
|
||||
NEUROLOGY = 'NEUROLOGY', 'Neurology'
|
||||
ONCOLOGY = 'ONCOLOGY', 'Oncology'
|
||||
ORTHOPEDICS = 'ORTHOPEDICS', 'Orthopedics'
|
||||
PSYCHIATRY = 'PSYCHIATRY', 'Psychiatry'
|
||||
RADIOLOGY = 'RADIOLOGY', 'Radiology'
|
||||
SURGERY = 'SURGERY', 'Surgery'
|
||||
UROLOGY = 'UROLOGY', 'Urology'
|
||||
GYNECOLOGY = 'GYNECOLOGY', 'Gynecology'
|
||||
OPHTHALMOLOGY = 'OPHTHALMOLOGY', 'Ophthalmology'
|
||||
ENT = 'ENT', 'Ear, Nose & Throat'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency Medicine'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class Priority(models.TextChoices):
|
||||
ROUTINE = 'ROUTINE', 'Routine'
|
||||
URGENT = 'URGENT', 'Urgent'
|
||||
STAT = 'STAT', 'STAT'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||
|
||||
class AppointmentStatus(models.TextChoices):
|
||||
PENDING = 'PENDING', 'Pending'
|
||||
SCHEDULED = 'SCHEDULED', 'Scheduled'
|
||||
CONFIRMED = 'CONFIRMED', 'Confirmed'
|
||||
CHECKED_IN = 'CHECKED_IN', 'Checked In'
|
||||
IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
|
||||
COMPLETED = 'COMPLETED', 'Completed'
|
||||
CANCELLED = 'CANCELLED', 'Cancelled'
|
||||
NO_SHOW = 'NO_SHOW', 'No Show'
|
||||
RESCHEDULED = 'RESCHEDULED', 'Rescheduled'
|
||||
|
||||
class TelemedicinePlatform(models.TextChoices):
|
||||
ZOOM = 'ZOOM', 'Zoom'
|
||||
TEAMS = 'TEAMS', 'Microsoft Teams'
|
||||
WEBEX = 'WEBEX', 'Cisco Webex'
|
||||
DOXY = 'DOXY', 'Doxy.me'
|
||||
CUSTOM = 'CUSTOM', 'Custom Platform'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
# Basic Identifiers
|
||||
request_id = models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
@ -110,13 +111,13 @@ class AppointmentRequest(models.Model):
|
||||
# Appointment Details
|
||||
appointment_type = models.CharField(
|
||||
max_length=50,
|
||||
choices=APPOINTMENT_TYPE_CHOICES,
|
||||
choices=AppointmentType.choices,
|
||||
help_text='Type of appointment'
|
||||
)
|
||||
|
||||
specialty = models.CharField(
|
||||
max_length=100,
|
||||
choices=SPECIALTY_CHOICES,
|
||||
choices=Specialty.choices,
|
||||
help_text='Medical specialty'
|
||||
)
|
||||
|
||||
@ -159,8 +160,8 @@ class AppointmentRequest(models.Model):
|
||||
# Priority and Urgency
|
||||
priority = models.CharField(
|
||||
max_length=20,
|
||||
choices=PRIORITY_CHOICES,
|
||||
default='ROUTINE',
|
||||
choices=Priority.choices,
|
||||
default=Priority.ROUTINE,
|
||||
help_text='Appointment priority'
|
||||
)
|
||||
urgency_score = models.PositiveIntegerField(
|
||||
@ -204,8 +205,8 @@ class AppointmentRequest(models.Model):
|
||||
# Status and Workflow
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='PENDING',
|
||||
choices=AppointmentStatus.choices,
|
||||
default=AppointmentStatus.PENDING,
|
||||
help_text='Appointment status'
|
||||
)
|
||||
|
||||
@ -242,7 +243,7 @@ class AppointmentRequest(models.Model):
|
||||
)
|
||||
telemedicine_platform = models.CharField(
|
||||
max_length=50,
|
||||
choices=TELEMEDICINE_PLATFORM_CHOICES,
|
||||
choices=TelemedicinePlatform.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Telemedicine platform'
|
||||
@ -398,13 +399,14 @@ class SlotAvailability(models.Model):
|
||||
"""
|
||||
Provider availability slots for appointment scheduling.
|
||||
"""
|
||||
AVAILABILITY_TYPE_CHOICES=[
|
||||
('REGULAR', 'Regular Hours'),
|
||||
('EXTENDED', 'Extended Hours'),
|
||||
('EMERGENCY', 'Emergency'),
|
||||
('ON_CALL', 'On Call'),
|
||||
('TELEMEDICINE', 'Telemedicine Only'),
|
||||
]
|
||||
|
||||
class AvailabilityType(models.TextChoices):
|
||||
REGULAR = 'REGULAR', 'Regular Hours'
|
||||
EXTENDED = 'EXTENDED', 'Extended Hours'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||
ON_CALL = 'ON_CALL', 'On Call'
|
||||
TELEMEDICINE = 'TELEMEDICINE', 'Telemedicine Only'
|
||||
|
||||
# Tenant relationship
|
||||
tenant = models.ForeignKey(
|
||||
'core.Tenant',
|
||||
@ -446,8 +448,8 @@ class SlotAvailability(models.Model):
|
||||
# Availability Type
|
||||
availability_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=AVAILABILITY_TYPE_CHOICES,
|
||||
default='REGULAR',
|
||||
choices=AvailabilityType.choices,
|
||||
default=AvailabilityType.REGULAR,
|
||||
help_text='Type of availability'
|
||||
)
|
||||
|
||||
@ -588,13 +590,14 @@ class WaitingQueue(models.Model):
|
||||
"""
|
||||
Waiting queue for managing patient flow.
|
||||
"""
|
||||
QUEUE_TYPE_CHOICES=[
|
||||
('PROVIDER', 'Provider Queue'),
|
||||
('SPECIALTY', 'Specialty Queue'),
|
||||
('LOCATION', 'Location Queue'),
|
||||
('PROCEDURE', 'Procedure Queue'),
|
||||
('EMERGENCY', 'Emergency Queue'),
|
||||
]
|
||||
|
||||
class QueueType(models.TextChoices):
|
||||
PROVIDER = 'PROVIDER', 'Provider Queue'
|
||||
SPECIALTY = 'SPECIALTY', 'Specialty Queue'
|
||||
LOCATION = 'LOCATION', 'Location Queue'
|
||||
PROCEDURE = 'PROCEDURE', 'Procedure Queue'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency Queue'
|
||||
|
||||
# Tenant relationship
|
||||
tenant = models.ForeignKey(
|
||||
'core.Tenant',
|
||||
@ -624,7 +627,7 @@ class WaitingQueue(models.Model):
|
||||
# Queue Type and Configuration
|
||||
queue_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=QUEUE_TYPE_CHOICES,
|
||||
choices=QueueType.choices,
|
||||
help_text='Type of queue'
|
||||
)
|
||||
|
||||
@ -726,14 +729,15 @@ class QueueEntry(models.Model):
|
||||
"""
|
||||
Individual entry in a waiting queue.
|
||||
"""
|
||||
STATUS_CHOICES=[
|
||||
('WAITING', 'Waiting'),
|
||||
('CALLED', 'Called'),
|
||||
('IN_SERVICE', 'In Service'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('LEFT', 'Left Queue'),
|
||||
('NO_SHOW', 'No Show'),
|
||||
]
|
||||
|
||||
class QueueStatus(models.TextChoices):
|
||||
WAITING = 'WAITING', 'Waiting'
|
||||
CALLED = 'CALLED', 'Called'
|
||||
IN_SERVICE = 'IN_SERVICE', 'In Service'
|
||||
COMPLETED = 'COMPLETED', 'Completed'
|
||||
LEFT = 'LEFT', 'Left Queue'
|
||||
NO_SHOW = 'NO_SHOW', 'No Show'
|
||||
|
||||
# Queue and Patient
|
||||
queue = models.ForeignKey(
|
||||
WaitingQueue,
|
||||
@ -795,8 +799,8 @@ class QueueEntry(models.Model):
|
||||
# Status
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='WAITING',
|
||||
choices=QueueStatus.choices,
|
||||
default=QueueStatus.WAITING,
|
||||
help_text='Queue entry status'
|
||||
)
|
||||
|
||||
@ -878,22 +882,23 @@ class TelemedicineSession(models.Model):
|
||||
"""
|
||||
Telemedicine session management.
|
||||
"""
|
||||
PLATFORM_CHOICES=[
|
||||
('ZOOM', 'Zoom'),
|
||||
('TEAMS', 'Microsoft Teams'),
|
||||
('WEBEX', 'Cisco Webex'),
|
||||
('DOXY', 'Doxy.me'),
|
||||
('CUSTOM', 'Custom Platform'),
|
||||
('OTHER', 'Other'),
|
||||
]
|
||||
STATUS_CHOICES=[
|
||||
('SCHEDULED', 'Scheduled'),
|
||||
('READY', 'Ready to Start'),
|
||||
('IN_PROGRESS', 'In Progress'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('FAILED', 'Failed'),
|
||||
]
|
||||
|
||||
class Platform(models.TextChoices):
|
||||
ZOOM = 'ZOOM', 'Zoom'
|
||||
TEAMS = 'TEAMS', 'Microsoft Teams'
|
||||
WEBEX = 'WEBEX', 'Cisco Webex'
|
||||
DOXY = 'DOXY', 'Doxy.me'
|
||||
CUSTOM = 'CUSTOM', 'Custom Platform'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class SessionStatus(models.TextChoices):
|
||||
SCHEDULED = 'SCHEDULED', 'Scheduled'
|
||||
READY = 'READY', 'Ready to Start'
|
||||
IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
|
||||
COMPLETED = 'COMPLETED', 'Completed'
|
||||
CANCELLED = 'CANCELLED', 'Cancelled'
|
||||
FAILED = 'FAILED', 'Failed'
|
||||
|
||||
# Session Information
|
||||
session_id = models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
@ -913,7 +918,7 @@ class TelemedicineSession(models.Model):
|
||||
# Platform Information
|
||||
platform = models.CharField(
|
||||
max_length=50,
|
||||
choices=PLATFORM_CHOICES,
|
||||
choices=Platform.choices,
|
||||
help_text='Telemedicine platform'
|
||||
)
|
||||
|
||||
@ -959,8 +964,8 @@ class TelemedicineSession(models.Model):
|
||||
# Session Status
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='SCHEDULED',
|
||||
choices=SessionStatus.choices,
|
||||
default=SessionStatus.SCHEDULED,
|
||||
help_text='Session status'
|
||||
)
|
||||
|
||||
@ -1182,85 +1187,85 @@ class WaitingList(models.Model):
|
||||
Patient waiting list for appointment scheduling.
|
||||
Follows healthcare industry standards for patient queue management.
|
||||
"""
|
||||
APPOINTMENT_TYPE_CHOICES = [
|
||||
('CONSULTATION', 'Consultation'),
|
||||
('FOLLOW_UP', 'Follow-up'),
|
||||
('PROCEDURE', 'Procedure'),
|
||||
('SURGERY', 'Surgery'),
|
||||
('DIAGNOSTIC', 'Diagnostic'),
|
||||
('THERAPY', 'Therapy'),
|
||||
('VACCINATION', 'Vaccination'),
|
||||
('SCREENING', 'Screening'),
|
||||
('EMERGENCY', 'Emergency'),
|
||||
('TELEMEDICINE', 'Telemedicine'),
|
||||
('OTHER', 'Other'),
|
||||
]
|
||||
SPECIALTY_CHOICES = [
|
||||
('FAMILY_MEDICINE', 'Family Medicine'),
|
||||
('INTERNAL_MEDICINE', 'Internal Medicine'),
|
||||
('PEDIATRICS', 'Pediatrics'),
|
||||
('CARDIOLOGY', 'Cardiology'),
|
||||
('DERMATOLOGY', 'Dermatology'),
|
||||
('ENDOCRINOLOGY', 'Endocrinology'),
|
||||
('GASTROENTEROLOGY', 'Gastroenterology'),
|
||||
('NEUROLOGY', 'Neurology'),
|
||||
('ONCOLOGY', 'Oncology'),
|
||||
('ORTHOPEDICS', 'Orthopedics'),
|
||||
('PSYCHIATRY', 'Psychiatry'),
|
||||
('RADIOLOGY', 'Radiology'),
|
||||
('SURGERY', 'Surgery'),
|
||||
('UROLOGY', 'Urology'),
|
||||
('GYNECOLOGY', 'Gynecology'),
|
||||
('OPHTHALMOLOGY', 'Ophthalmology'),
|
||||
('ENT', 'Ear, Nose & Throat'),
|
||||
('EMERGENCY', 'Emergency Medicine'),
|
||||
('OTHER', 'Other'),
|
||||
]
|
||||
PRIORITY_CHOICES = [
|
||||
('ROUTINE', 'Routine'),
|
||||
('URGENT', 'Urgent'),
|
||||
('STAT', 'STAT'),
|
||||
('EMERGENCY', 'Emergency'),
|
||||
]
|
||||
CONTACT_METHOD_CHOICES = [
|
||||
('PHONE', 'Phone'),
|
||||
('EMAIL', 'Email'),
|
||||
('SMS', 'SMS'),
|
||||
('PORTAL', 'Patient Portal'),
|
||||
('MAIL', 'Mail'),
|
||||
]
|
||||
STATUS_CHOICES = [
|
||||
('ACTIVE', 'Active'),
|
||||
('CONTACTED', 'Contacted'),
|
||||
('OFFERED', 'Appointment Offered'),
|
||||
('SCHEDULED', 'Scheduled'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('EXPIRED', 'Expired'),
|
||||
('TRANSFERRED', 'Transferred'),
|
||||
]
|
||||
AUTHORIZATION_STATUS_CHOICES = [
|
||||
('NOT_REQUIRED', 'Not Required'),
|
||||
('PENDING', 'Pending'),
|
||||
('APPROVED', 'Approved'),
|
||||
('DENIED', 'Denied'),
|
||||
('EXPIRED', 'Expired'),
|
||||
]
|
||||
REFERRAL_URGENCY_CHOICES = [
|
||||
('ROUTINE', 'Routine'),
|
||||
('URGENT', 'Urgent'),
|
||||
('STAT', 'STAT'),
|
||||
]
|
||||
REMOVAL_REASON_CHOICES = [
|
||||
('SCHEDULED', 'Appointment Scheduled'),
|
||||
('PATIENT_CANCELLED', 'Patient Cancelled'),
|
||||
('PROVIDER_CANCELLED', 'Provider Cancelled'),
|
||||
('NO_RESPONSE', 'No Response to Contact'),
|
||||
('INSURANCE_ISSUE', 'Insurance Issue'),
|
||||
('TRANSFERRED', 'Transferred to Another Provider'),
|
||||
('EXPIRED', 'Entry Expired'),
|
||||
('DUPLICATE', 'Duplicate Entry'),
|
||||
('OTHER', 'Other'),
|
||||
]
|
||||
|
||||
class AppointmentType(models.TextChoices):
|
||||
CONSULTATION = 'CONSULTATION', 'Consultation'
|
||||
FOLLOW_UP = 'FOLLOW_UP', 'Follow-up'
|
||||
PROCEDURE = 'PROCEDURE', 'Procedure'
|
||||
SURGERY = 'SURGERY', 'Surgery'
|
||||
DIAGNOSTIC = 'DIAGNOSTIC', 'Diagnostic'
|
||||
THERAPY = 'THERAPY', 'Therapy'
|
||||
VACCINATION = 'VACCINATION', 'Vaccination'
|
||||
SCREENING = 'SCREENING', 'Screening'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||
TELEMEDICINE = 'TELEMEDICINE', 'Telemedicine'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class Specialty(models.TextChoices):
|
||||
FAMILY_MEDICINE = 'FAMILY_MEDICINE', 'Family Medicine'
|
||||
INTERNAL_MEDICINE = 'INTERNAL_MEDICINE', 'Internal Medicine'
|
||||
PEDIATRICS = 'PEDIATRICS', 'Pediatrics'
|
||||
CARDIOLOGY = 'CARDIOLOGY', 'Cardiology'
|
||||
DERMATOLOGY = 'DERMATOLOGY', 'Dermatology'
|
||||
ENDOCRINOLOGY = 'ENDOCRINOLOGY', 'Endocrinology'
|
||||
GASTROENTEROLOGY = 'GASTROENTEROLOGY', 'Gastroenterology'
|
||||
NEUROLOGY = 'NEUROLOGY', 'Neurology'
|
||||
ONCOLOGY = 'ONCOLOGY', 'Oncology'
|
||||
ORTHOPEDICS = 'ORTHOPEDICS', 'Orthopedics'
|
||||
PSYCHIATRY = 'PSYCHIATRY', 'Psychiatry'
|
||||
RADIOLOGY = 'RADIOLOGY', 'Radiology'
|
||||
SURGERY = 'SURGERY', 'Surgery'
|
||||
UROLOGY = 'UROLOGY', 'Urology'
|
||||
GYNECOLOGY = 'GYNECOLOGY', 'Gynecology'
|
||||
OPHTHALMOLOGY = 'OPHTHALMOLOGY', 'Ophthalmology'
|
||||
ENT = 'ENT', 'Ear, Nose & Throat'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency Medicine'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class Priority(models.TextChoices):
|
||||
ROUTINE = 'ROUTINE', 'Routine'
|
||||
URGENT = 'URGENT', 'Urgent'
|
||||
STAT = 'STAT', 'STAT'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||
|
||||
class ContactMethod(models.TextChoices):
|
||||
PHONE = 'PHONE', 'Phone'
|
||||
EMAIL = 'EMAIL', 'Email'
|
||||
SMS = 'SMS', 'SMS'
|
||||
PORTAL = 'PORTAL', 'Patient Portal'
|
||||
MAIL = 'MAIL', 'Mail'
|
||||
|
||||
class RequestStatus(models.TextChoices):
|
||||
ACTIVE = 'ACTIVE', 'Active'
|
||||
CONTACTED = 'CONTACTED', 'Contacted'
|
||||
OFFERED = 'OFFERED', 'Appointment Offered'
|
||||
SCHEDULED = 'SCHEDULED', 'Scheduled'
|
||||
CANCELLED = 'CANCELLED', 'Cancelled'
|
||||
EXPIRED = 'EXPIRED', 'Expired'
|
||||
TRANSFERRED = 'TRANSFERRED', 'Transferred'
|
||||
|
||||
class AuthorizationStatus(models.TextChoices):
|
||||
NOT_REQUIRED = 'NOT_REQUIRED', 'Not Required'
|
||||
PENDING = 'PENDING', 'Pending'
|
||||
APPROVED = 'APPROVED', 'Approved'
|
||||
DENIED = 'DENIED', 'Denied'
|
||||
EXPIRED = 'EXPIRED', 'Expired'
|
||||
|
||||
class ReferralUrgency(models.TextChoices):
|
||||
ROUTINE = 'ROUTINE', 'Routine'
|
||||
URGENT = 'URGENT', 'Urgent'
|
||||
STAT = 'STAT', 'STAT'
|
||||
|
||||
class RemovalReason(models.TextChoices):
|
||||
SCHEDULED = 'SCHEDULED', 'Appointment Scheduled'
|
||||
PATIENT_CANCELLED = 'PATIENT_CANCELLED', 'Patient Cancelled'
|
||||
PROVIDER_CANCELLED = 'PROVIDER_CANCELLED', 'Provider Cancelled'
|
||||
NO_RESPONSE = 'NO_RESPONSE', 'No Response to Contact'
|
||||
INSURANCE_ISSUE = 'INSURANCE_ISSUE', 'Insurance Issue'
|
||||
TRANSFERRED = 'TRANSFERRED', 'Transferred to Another Provider'
|
||||
EXPIRED = 'EXPIRED', 'Entry Expired'
|
||||
DUPLICATE = 'DUPLICATE', 'Duplicate Entry'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
# Basic Identifiers
|
||||
waiting_list_id = models.UUIDField(
|
||||
@ -1305,21 +1310,21 @@ class WaitingList(models.Model):
|
||||
|
||||
appointment_type = models.CharField(
|
||||
max_length=50,
|
||||
choices=APPOINTMENT_TYPE_CHOICES,
|
||||
choices=AppointmentType.choices,
|
||||
help_text='Type of appointment requested'
|
||||
)
|
||||
|
||||
specialty = models.CharField(
|
||||
max_length=100,
|
||||
choices=SPECIALTY_CHOICES,
|
||||
choices=Specialty.choices,
|
||||
help_text='Medical specialty required'
|
||||
)
|
||||
|
||||
# Priority and Clinical Information
|
||||
priority = models.CharField(
|
||||
max_length=20,
|
||||
choices=PRIORITY_CHOICES,
|
||||
default='ROUTINE',
|
||||
choices=Priority.choices,
|
||||
default=Priority.ROUTINE,
|
||||
help_text='Clinical priority level'
|
||||
)
|
||||
|
||||
@ -1385,8 +1390,8 @@ class WaitingList(models.Model):
|
||||
# Communication Preferences
|
||||
contact_method = models.CharField(
|
||||
max_length=20,
|
||||
choices=CONTACT_METHOD_CHOICES,
|
||||
default='PHONE',
|
||||
choices=ContactMethod.choices,
|
||||
default=ContactMethod.PHONE,
|
||||
help_text='Preferred contact method'
|
||||
)
|
||||
|
||||
@ -1406,8 +1411,8 @@ class WaitingList(models.Model):
|
||||
# Status and Workflow
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=STATUS_CHOICES,
|
||||
default='ACTIVE',
|
||||
choices=RequestStatus.choices,
|
||||
default=RequestStatus.ACTIVE,
|
||||
help_text='Waiting list status'
|
||||
)
|
||||
|
||||
@ -1495,8 +1500,8 @@ class WaitingList(models.Model):
|
||||
|
||||
authorization_status = models.CharField(
|
||||
max_length=20,
|
||||
choices=AUTHORIZATION_STATUS_CHOICES,
|
||||
default='NOT_REQUIRED',
|
||||
choices=AuthorizationStatus.choices,
|
||||
default=AuthorizationStatus.NOT_REQUIRED,
|
||||
help_text='Authorization status'
|
||||
)
|
||||
|
||||
@ -1523,8 +1528,8 @@ class WaitingList(models.Model):
|
||||
|
||||
referral_urgency = models.CharField(
|
||||
max_length=20,
|
||||
choices=REFERRAL_URGENCY_CHOICES,
|
||||
default='ROUTINE',
|
||||
choices=ReferralUrgency.choices,
|
||||
default=ReferralUrgency.ROUTINE,
|
||||
help_text='Referral urgency level'
|
||||
)
|
||||
|
||||
@ -1540,7 +1545,7 @@ class WaitingList(models.Model):
|
||||
|
||||
removal_reason = models.CharField(
|
||||
max_length=50,
|
||||
choices=REMOVAL_REASON_CHOICES,
|
||||
choices=RemovalReason.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Reason for removal from waiting list'
|
||||
@ -1701,35 +1706,35 @@ class WaitingListContactLog(models.Model):
|
||||
Contact log for waiting list entries.
|
||||
Tracks all communication attempts with patients on waiting list.
|
||||
"""
|
||||
CONTACT_METHOD_CHOICES = [
|
||||
('PHONE', 'Phone Call'),
|
||||
('EMAIL', 'Email'),
|
||||
('SMS', 'SMS'),
|
||||
('PORTAL', 'Patient Portal Message'),
|
||||
('MAIL', 'Mail'),
|
||||
('IN_PERSON', 'In Person'),
|
||||
]
|
||||
CONTACT_OUTCOME_CHOICES = [
|
||||
('SUCCESSFUL', 'Successful Contact'),
|
||||
('NO_ANSWER', 'No Answer'),
|
||||
('BUSY', 'Line Busy'),
|
||||
('VOICEMAIL', 'Left Voicemail'),
|
||||
('EMAIL_SENT', 'Email Sent'),
|
||||
('EMAIL_BOUNCED', 'Email Bounced'),
|
||||
('SMS_SENT', 'SMS Sent'),
|
||||
('SMS_FAILED', 'SMS Failed'),
|
||||
('WRONG_NUMBER', 'Wrong Number'),
|
||||
('DECLINED', 'Patient Declined'),
|
||||
]
|
||||
PATIENT_RESPONSE_CHOICES = [
|
||||
('ACCEPTED', 'Accepted Appointment'),
|
||||
('DECLINED', 'Declined Appointment'),
|
||||
('REQUESTED_DIFFERENT', 'Requested Different Time'),
|
||||
('WILL_CALL_BACK', 'Will Call Back'),
|
||||
('NO_LONGER_NEEDED', 'No Longer Needed'),
|
||||
('INSURANCE_ISSUE', 'Insurance Issue'),
|
||||
('NO_RESPONSE', 'No Response'),
|
||||
]
|
||||
|
||||
class ContactMethod(models.TextChoices):
|
||||
PHONE = 'PHONE', 'Phone Call'
|
||||
EMAIL = 'EMAIL', 'Email'
|
||||
SMS = 'SMS', 'SMS'
|
||||
PORTAL = 'PORTAL', 'Patient Portal Message'
|
||||
MAIL = 'MAIL', 'Mail'
|
||||
IN_PERSON = 'IN_PERSON', 'In Person'
|
||||
|
||||
class ContactOutcome(models.TextChoices):
|
||||
SUCCESSFUL = 'SUCCESSFUL', 'Successful Contact'
|
||||
NO_ANSWER = 'NO_ANSWER', 'No Answer'
|
||||
BUSY = 'BUSY', 'Line Busy'
|
||||
VOICEMAIL = 'VOICEMAIL', 'Left Voicemail'
|
||||
EMAIL_SENT = 'EMAIL_SENT', 'Email Sent'
|
||||
EMAIL_BOUNCED = 'EMAIL_BOUNCED', 'Email Bounced'
|
||||
SMS_SENT = 'SMS_SENT', 'SMS Sent'
|
||||
SMS_FAILED = 'SMS_FAILED', 'SMS Failed'
|
||||
WRONG_NUMBER = 'WRONG_NUMBER', 'Wrong Number'
|
||||
DECLINED = 'DECLINED', 'Patient Declined'
|
||||
|
||||
class PatientResponse(models.TextChoices):
|
||||
ACCEPTED = 'ACCEPTED', 'Accepted Appointment'
|
||||
DECLINED = 'DECLINED', 'Declined Appointment'
|
||||
REQUESTED_DIFFERENT = 'REQUESTED_DIFFERENT', 'Requested Different Time'
|
||||
WILL_CALL_BACK = 'WILL_CALL_BACK', 'Will Call Back'
|
||||
NO_LONGER_NEEDED = 'NO_LONGER_NEEDED', 'No Longer Needed'
|
||||
INSURANCE_ISSUE = 'INSURANCE_ISSUE', 'Insurance Issue'
|
||||
NO_RESPONSE = 'NO_RESPONSE', 'No Response'
|
||||
|
||||
waiting_list_entry = models.ForeignKey(
|
||||
WaitingList,
|
||||
@ -1745,13 +1750,13 @@ class WaitingListContactLog(models.Model):
|
||||
|
||||
contact_method = models.CharField(
|
||||
max_length=20,
|
||||
choices=CONTACT_METHOD_CHOICES,
|
||||
choices=ContactMethod.choices,
|
||||
help_text='Method of contact used'
|
||||
)
|
||||
|
||||
contact_outcome = models.CharField(
|
||||
max_length=20,
|
||||
choices=CONTACT_OUTCOME_CHOICES,
|
||||
choices=ContactOutcome.choices,
|
||||
help_text='Outcome of contact attempt'
|
||||
)
|
||||
|
||||
@ -1774,7 +1779,7 @@ class WaitingListContactLog(models.Model):
|
||||
|
||||
patient_response = models.CharField(
|
||||
max_length=20,
|
||||
choices=PATIENT_RESPONSE_CHOICES,
|
||||
choices=PatientResponse.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Patient response to contact'
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
{% block title %}Slot Availability - Appointment Management{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link href="{% static 'assets/plugins/fullcalendar/main.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'assets/plugins/select2/css/select2.min.css' %}" rel="stylesheet" />
|
||||
{% block css %}
|
||||
<link href="{% static 'plugins/@fullcalendar/main.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/select2/css/select2.min.css' %}" rel="stylesheet" />
|
||||
<style>
|
||||
.availability-header {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
|
||||
@ -18,7 +18,7 @@ from core.models import Tenant
|
||||
from hr.models import Employee, Department
|
||||
import uuid
|
||||
|
||||
User = get_user_model()
|
||||
from accounts.models import User
|
||||
|
||||
# Saudi-specific healthcare data
|
||||
SAUDI_SPECIALTIES = [
|
||||
@ -134,8 +134,8 @@ def get_providers_for_tenant(tenants):
|
||||
if not providers:
|
||||
for tenant in tenants:
|
||||
tenant_providers = User.objects.filter(
|
||||
employee_profile__tenant=tenant,
|
||||
role__in=['PHYSICIAN', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT', 'RADIOLOGIST', 'SURGEON']
|
||||
tenant=tenant,
|
||||
employee_profile__role__in=['PHYSICIAN', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT', 'RADIOLOGIST', 'SURGEON']
|
||||
).select_related('employee_profile')
|
||||
providers.extend(list(tenant_providers))
|
||||
|
||||
@ -145,7 +145,7 @@ def get_providers_for_tenant(tenants):
|
||||
tenant_providers = User.objects.filter(
|
||||
tenant=tenant,
|
||||
is_active=True,
|
||||
role__icontains='PHYSICIAN'
|
||||
employee_profile__role__icontains='PHYSICIAN'
|
||||
)
|
||||
providers.extend(list(tenant_providers))
|
||||
|
||||
|
||||
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import billing.utils
|
||||
import django.db.models.deletion
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
@ -12,8 +12,6 @@ class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("billing", "0001_initial"),
|
||||
("core", "0001_initial"),
|
||||
("emr", "0001_initial"),
|
||||
("inpatients", "0001_initial"),
|
||||
("patients", "0001_initial"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
@ -109,310 +107,4 @@ class Migration(migrations.Migration):
|
||||
to="billing.insuranceclaim",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="admission",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Related admission",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="medical_bills",
|
||||
to="inpatients.admission",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="attending_provider",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Attending provider",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="attending_bills",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="billing_provider",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Billing provider",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="billing_provider_bills",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="created_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="User who created the bill",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="created_medical_bills",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="encounter",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Related encounter",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="medical_bills",
|
||||
to="emr.encounter",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="patient",
|
||||
field=models.ForeignKey(
|
||||
help_text="Patient",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="medical_bills",
|
||||
to="patients.patientprofile",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="primary_insurance",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Primary insurance",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="primary_bills",
|
||||
to="patients.insuranceinfo",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="secondary_insurance",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Secondary insurance",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="secondary_bills",
|
||||
to="patients.insuranceinfo",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="tenant",
|
||||
field=models.ForeignKey(
|
||||
help_text="Organization tenant",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="medical_bills",
|
||||
to="core.tenant",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="insuranceclaim",
|
||||
name="medical_bill",
|
||||
field=models.ForeignKey(
|
||||
help_text="Related medical bill",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="insurance_claims",
|
||||
to="billing.medicalbill",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="billlineitem",
|
||||
name="medical_bill",
|
||||
field=models.ForeignKey(
|
||||
help_text="Medical bill",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="line_items",
|
||||
to="billing.medicalbill",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="payment",
|
||||
name="insurance_claim",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Related insurance claim",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="payments",
|
||||
to="billing.insuranceclaim",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="payment",
|
||||
name="medical_bill",
|
||||
field=models.ForeignKey(
|
||||
help_text="Related medical bill",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="payments",
|
||||
to="billing.medicalbill",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="payment",
|
||||
name="processed_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Staff member who processed payment",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="processed_payments",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="payment",
|
||||
name="received_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Staff member who received payment",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="received_payments",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="claimstatusupdate",
|
||||
index=models.Index(
|
||||
fields=["insurance_claim"], name="billing_cla_insuran_7c9e49_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="claimstatusupdate",
|
||||
index=models.Index(
|
||||
fields=["status_date"], name="billing_cla_status__49d81f_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="claimstatusupdate",
|
||||
index=models.Index(
|
||||
fields=["new_status"], name="billing_cla_new_sta_9c28d3_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["tenant", "status"], name="billing_med_tenant__fe8d14_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["patient", "bill_date"], name="billing_med_patient_8f1a85_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["bill_number"], name="billing_med_bill_nu_f01dfa_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["status", "due_date"], name="billing_med_status_cde77f_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["collection_status"], name="billing_med_collect_6d0faf_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["medical_bill"], name="billing_ins_medical_1fec52_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["insurance_info"], name="billing_ins_insuran_e54611_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["claim_number"], name="billing_ins_claim_n_becaa3_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["status", "submission_date"],
|
||||
name="billing_ins_status_921ea7_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["response_date"], name="billing_ins_respons_fc4f3d_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="billlineitem",
|
||||
index=models.Index(
|
||||
fields=["medical_bill", "line_number"],
|
||||
name="billing_bil_medical_37a377_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="billlineitem",
|
||||
index=models.Index(
|
||||
fields=["service_code"], name="billing_bil_service_b88f5b_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="billlineitem",
|
||||
index=models.Index(
|
||||
fields=["service_date"], name="billing_bil_service_658c36_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="billlineitem",
|
||||
index=models.Index(
|
||||
fields=["rendering_provider"], name="billing_bil_renderi_2740ad_idx"
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="billlineitem",
|
||||
unique_together={("medical_bill", "line_number")},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(
|
||||
fields=["medical_bill"], name="billing_pay_medical_e4e348_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(
|
||||
fields=["payment_number"], name="billing_pay_payment_0825d6_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(
|
||||
fields=["payment_date"], name="billing_pay_payment_bed741_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(
|
||||
fields=["payment_method"], name="billing_pay_payment_715e62_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(fields=["status"], name="billing_pay_status_b7739e_idx"),
|
||||
),
|
||||
]
|
||||
|
||||
328
billing/migrations/0003_initial.py
Normal file
328
billing/migrations/0003_initial.py
Normal file
@ -0,0 +1,328 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("billing", "0002_initial"),
|
||||
("core", "0001_initial"),
|
||||
("emr", "0001_initial"),
|
||||
("inpatients", "0001_initial"),
|
||||
("patients", "0001_initial"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="admission",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Related admission",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="medical_bills",
|
||||
to="inpatients.admission",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="attending_provider",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Attending provider",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="attending_bills",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="billing_provider",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Billing provider",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="billing_provider_bills",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="created_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="User who created the bill",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="created_medical_bills",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="encounter",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Related encounter",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="medical_bills",
|
||||
to="emr.encounter",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="patient",
|
||||
field=models.ForeignKey(
|
||||
help_text="Patient",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="medical_bills",
|
||||
to="patients.patientprofile",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="primary_insurance",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Primary insurance",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="primary_bills",
|
||||
to="patients.insuranceinfo",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="secondary_insurance",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Secondary insurance",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="secondary_bills",
|
||||
to="patients.insuranceinfo",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="medicalbill",
|
||||
name="tenant",
|
||||
field=models.ForeignKey(
|
||||
help_text="Organization tenant",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="medical_bills",
|
||||
to="core.tenant",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="insuranceclaim",
|
||||
name="medical_bill",
|
||||
field=models.ForeignKey(
|
||||
help_text="Related medical bill",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="insurance_claims",
|
||||
to="billing.medicalbill",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="billlineitem",
|
||||
name="medical_bill",
|
||||
field=models.ForeignKey(
|
||||
help_text="Medical bill",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="line_items",
|
||||
to="billing.medicalbill",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="payment",
|
||||
name="insurance_claim",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Related insurance claim",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="payments",
|
||||
to="billing.insuranceclaim",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="payment",
|
||||
name="medical_bill",
|
||||
field=models.ForeignKey(
|
||||
help_text="Related medical bill",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="payments",
|
||||
to="billing.medicalbill",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="payment",
|
||||
name="processed_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Staff member who processed payment",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="processed_payments",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="payment",
|
||||
name="received_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Staff member who received payment",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="received_payments",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="claimstatusupdate",
|
||||
index=models.Index(
|
||||
fields=["insurance_claim"], name="billing_cla_insuran_7c9e49_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="claimstatusupdate",
|
||||
index=models.Index(
|
||||
fields=["status_date"], name="billing_cla_status__49d81f_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="claimstatusupdate",
|
||||
index=models.Index(
|
||||
fields=["new_status"], name="billing_cla_new_sta_9c28d3_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["tenant", "status"], name="billing_med_tenant__fe8d14_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["patient", "bill_date"], name="billing_med_patient_8f1a85_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["bill_number"], name="billing_med_bill_nu_f01dfa_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["status", "due_date"], name="billing_med_status_cde77f_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="medicalbill",
|
||||
index=models.Index(
|
||||
fields=["collection_status"], name="billing_med_collect_6d0faf_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["medical_bill"], name="billing_ins_medical_1fec52_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["insurance_info"], name="billing_ins_insuran_e54611_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["claim_number"], name="billing_ins_claim_n_becaa3_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["status", "submission_date"],
|
||||
name="billing_ins_status_921ea7_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="insuranceclaim",
|
||||
index=models.Index(
|
||||
fields=["response_date"], name="billing_ins_respons_fc4f3d_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="billlineitem",
|
||||
index=models.Index(
|
||||
fields=["medical_bill", "line_number"],
|
||||
name="billing_bil_medical_37a377_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="billlineitem",
|
||||
index=models.Index(
|
||||
fields=["service_code"], name="billing_bil_service_b88f5b_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="billlineitem",
|
||||
index=models.Index(
|
||||
fields=["service_date"], name="billing_bil_service_658c36_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="billlineitem",
|
||||
index=models.Index(
|
||||
fields=["rendering_provider"], name="billing_bil_renderi_2740ad_idx"
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="billlineitem",
|
||||
unique_together={("medical_bill", "line_number")},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(
|
||||
fields=["medical_bill"], name="billing_pay_medical_e4e348_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(
|
||||
fields=["payment_number"], name="billing_pay_payment_0825d6_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(
|
||||
fields=["payment_date"], name="billing_pay_payment_bed741_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(
|
||||
fields=["payment_method"], name="billing_pay_payment_715e62_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="payment",
|
||||
index=models.Index(fields=["status"], name="billing_pay_status_b7739e_idx"),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
BIN
billing/migrations/__pycache__/0003_initial.cpython-312.pyc
Normal file
BIN
billing/migrations/__pycache__/0003_initial.cpython-312.pyc
Normal file
Binary file not shown.
@ -15,12 +15,10 @@ from core.models import Tenant
|
||||
from patients.models import PatientProfile, InsuranceInfo
|
||||
from emr.models import Encounter
|
||||
from inpatients.models import Admission
|
||||
from billing.models import (
|
||||
MedicalBill, BillLineItem, InsuranceClaim, Payment,
|
||||
ClaimStatusUpdate, BillingConfiguration
|
||||
)
|
||||
from billing.models import *
|
||||
from accounts.models import User
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import DecimalField, CharField, IntegerField
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
@ -31,12 +31,12 @@ class Migration(migrations.Migration):
|
||||
"name",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("whole_blood", "Whole Blood"),
|
||||
("packed_rbc", "Packed Red Blood Cells"),
|
||||
("fresh_frozen_plasma", "Fresh Frozen Plasma"),
|
||||
("platelets", "Platelets"),
|
||||
("cryoprecipitate", "Cryoprecipitate"),
|
||||
("granulocytes", "Granulocytes"),
|
||||
("WHOLE_BLOOD", "Whole Blood"),
|
||||
("PACKED_RBC", "Packed Red Blood Cells"),
|
||||
("FRESH_FROZEN_PLASMA", "Fresh Frozen Plasma"),
|
||||
("PLATELETS", "Platelets"),
|
||||
("CRYOPRECIPITATE", "Cryoprecipitate"),
|
||||
("GRANULOCYTES", "Granulocytes"),
|
||||
],
|
||||
max_length=50,
|
||||
unique=True,
|
||||
@ -68,15 +68,15 @@ class Migration(migrations.Migration):
|
||||
"test_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("abo_rh", "ABO/Rh Typing"),
|
||||
("antibody_screen", "Antibody Screening"),
|
||||
("hiv", "HIV"),
|
||||
("hbv", "Hepatitis B"),
|
||||
("hcv", "Hepatitis C"),
|
||||
("syphilis", "Syphilis"),
|
||||
("htlv", "HTLV"),
|
||||
("cmv", "CMV"),
|
||||
("malaria", "Malaria"),
|
||||
("ABO_RH", "ABO/Rh Typing"),
|
||||
("ANTIBODY_SCREEN", "Antibody Screening"),
|
||||
("HIV", "HIV"),
|
||||
("HBV", "Hepatitis B"),
|
||||
("HCV", "Hepatitis C"),
|
||||
("SYPHILIS", "Syphilis"),
|
||||
("HTLV", "HTLV"),
|
||||
("CMV", "CMV"),
|
||||
("MALARIA", "Malaria"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
@ -85,12 +85,12 @@ class Migration(migrations.Migration):
|
||||
"result",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("positive", "Positive"),
|
||||
("negative", "Negative"),
|
||||
("indeterminate", "Indeterminate"),
|
||||
("pending", "Pending"),
|
||||
("POSITIVE", "Positive"),
|
||||
("NEGATIVE", "Negative"),
|
||||
("INDETERMINATE", "Indeterminate"),
|
||||
("PENDING", "Pending"),
|
||||
],
|
||||
default="pending",
|
||||
default="PENDING",
|
||||
max_length=15,
|
||||
),
|
||||
),
|
||||
@ -124,17 +124,17 @@ class Migration(migrations.Migration):
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("collected", "Collected"),
|
||||
("testing", "Testing"),
|
||||
("quarantine", "Quarantine"),
|
||||
("available", "Available"),
|
||||
("reserved", "Reserved"),
|
||||
("issued", "Issued"),
|
||||
("transfused", "Transfused"),
|
||||
("expired", "Expired"),
|
||||
("discarded", "Discarded"),
|
||||
("COLLECTED", "Collected"),
|
||||
("TESTING", "Testing"),
|
||||
("QUARANTINE", "Quarantine"),
|
||||
("AVAILABLE", "Available"),
|
||||
("RESERVED", "Reserved"),
|
||||
("ISSUED", "Issued"),
|
||||
("TRANSFUSED", "Transfused"),
|
||||
("EXPIRED", "Expired"),
|
||||
("DISCARDED", "Discarded"),
|
||||
],
|
||||
default="collected",
|
||||
default="COLLECTED",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
@ -166,10 +166,10 @@ class Migration(migrations.Migration):
|
||||
"test_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("major", "Major Crossmatch"),
|
||||
("minor", "Minor Crossmatch"),
|
||||
("immediate_spin", "Immediate Spin"),
|
||||
("antiglobulin", "Antiglobulin Test"),
|
||||
("MAJOR", "Major Crossmatch"),
|
||||
("MINOR", "Minor Crossmatch"),
|
||||
("IMMEDIATE_SPIN", "Immediate Spin"),
|
||||
("ANTIGLOBULIN", "Antiglobulin Test"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
@ -178,11 +178,11 @@ class Migration(migrations.Migration):
|
||||
"compatibility",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("compatible", "Compatible"),
|
||||
("incompatible", "Incompatible"),
|
||||
("pending", "Pending"),
|
||||
("COMPATIBLE", "Compatible"),
|
||||
("INCOMPATIBLE", "Incompatible"),
|
||||
("PENDING", "Pending"),
|
||||
],
|
||||
default="pending",
|
||||
default="PENDING",
|
||||
max_length=15,
|
||||
),
|
||||
),
|
||||
@ -215,7 +215,11 @@ class Migration(migrations.Migration):
|
||||
(
|
||||
"gender",
|
||||
models.CharField(
|
||||
choices=[("M", "Male"), ("F", "Female"), ("O", "Other")],
|
||||
choices=[
|
||||
("MALE", "Male"),
|
||||
("FEMALE", "Female"),
|
||||
("OTHER", "Other"),
|
||||
],
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
@ -229,12 +233,12 @@ class Migration(migrations.Migration):
|
||||
"donor_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("voluntary", "Voluntary"),
|
||||
("replacement", "Replacement"),
|
||||
("autologous", "Autologous"),
|
||||
("directed", "Directed"),
|
||||
("VOLUNTARY", "Voluntary"),
|
||||
("REPLACEMENT", "Replacement"),
|
||||
("AUTOLOGOUS", "Autologous"),
|
||||
("DIRECTED", "Directed"),
|
||||
],
|
||||
default="voluntary",
|
||||
default="VOLUNTARY",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
@ -242,12 +246,12 @@ class Migration(migrations.Migration):
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("active", "Active"),
|
||||
("deferred", "Deferred"),
|
||||
("permanently_deferred", "Permanently Deferred"),
|
||||
("inactive", "Inactive"),
|
||||
("ACTIVE", "Active"),
|
||||
("DEFERRED", "Deferred"),
|
||||
("PERMANENTLY_DEFERRED", "Permanently Deferred"),
|
||||
("INACTIVE", "Inactive"),
|
||||
],
|
||||
default="active",
|
||||
default="ACTIVE",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
@ -291,11 +295,11 @@ class Migration(migrations.Migration):
|
||||
"location_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("refrigerator", "Refrigerator"),
|
||||
("freezer", "Freezer"),
|
||||
("platelet_agitator", "Platelet Agitator"),
|
||||
("quarantine", "Quarantine"),
|
||||
("testing", "Testing Area"),
|
||||
("REFRIGERATOR", "Refrigerator"),
|
||||
("FREEZER", "Freezer"),
|
||||
("PLATELET_AGITATOR", "Platelet Agitator"),
|
||||
("QUARANTINE", "Quarantine"),
|
||||
("TESTING", "Testing Area"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
@ -327,11 +331,11 @@ class Migration(migrations.Migration):
|
||||
"test_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("temperature_monitoring", "Temperature Monitoring"),
|
||||
("equipment_calibration", "Equipment Calibration"),
|
||||
("reagent_testing", "Reagent Testing"),
|
||||
("proficiency_testing", "Proficiency Testing"),
|
||||
("process_validation", "Process Validation"),
|
||||
("TEMPERATURE_MONITORING", "Temperature Monitoring"),
|
||||
("EQUIPMENT_CALIBRATION", "Equipment Calibration"),
|
||||
("REAGENT_TESTING", "Reagent Testing"),
|
||||
("PROFICIENCY_TESTING", "Proficiency Testing"),
|
||||
("PROCESS_VALIDATION", "Process Validation"),
|
||||
],
|
||||
max_length=30,
|
||||
),
|
||||
@ -345,9 +349,9 @@ class Migration(migrations.Migration):
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("pass", "Pass"),
|
||||
("fail", "Fail"),
|
||||
("pending", "Pending"),
|
||||
("PASS", "Pass"),
|
||||
("FAIL", "Fail"),
|
||||
("PENDING", "Pending"),
|
||||
],
|
||||
max_length=10,
|
||||
),
|
||||
@ -363,9 +367,9 @@ class Migration(migrations.Migration):
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("low", "Low"),
|
||||
("medium", "Medium"),
|
||||
("high", "High"),
|
||||
("LOW", "Low"),
|
||||
("MEDIUM", "Medium"),
|
||||
("HIGH", "High"),
|
||||
],
|
||||
max_length=10,
|
||||
),
|
||||
@ -377,9 +381,9 @@ class Migration(migrations.Migration):
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("open", "Open"),
|
||||
("in_progress", "In Progress"),
|
||||
("closed", "Closed"),
|
||||
("OPEN", "Open"),
|
||||
("IN_PROGRESS", "In Progress"),
|
||||
("CLOSED", "Closed"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
@ -407,13 +411,13 @@ class Migration(migrations.Migration):
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("started", "Started"),
|
||||
("in_progress", "In Progress"),
|
||||
("completed", "Completed"),
|
||||
("stopped", "Stopped"),
|
||||
("adverse_reaction", "Adverse Reaction"),
|
||||
("STARTED", "Started"),
|
||||
("IN_PROGRESS", "In Progress"),
|
||||
("COMPLETED", "Completed"),
|
||||
("STOPPED", "Stopped"),
|
||||
("ADVERSE_REACTION", "Adverse Reaction"),
|
||||
],
|
||||
default="started",
|
||||
default="STARTED",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
@ -463,15 +467,15 @@ class Migration(migrations.Migration):
|
||||
"reaction_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("febrile", "Febrile Non-Hemolytic"),
|
||||
("allergic", "Allergic"),
|
||||
("hemolytic_acute", "Acute Hemolytic"),
|
||||
("hemolytic_delayed", "Delayed Hemolytic"),
|
||||
("anaphylactic", "Anaphylactic"),
|
||||
("septic", "Septic"),
|
||||
("circulatory_overload", "Circulatory Overload"),
|
||||
("lung_injury", "Transfusion-Related Acute Lung Injury"),
|
||||
("other", "Other"),
|
||||
("FEBRILE", "Febrile Non-Hemolytic"),
|
||||
("ALLERGIC", "Allergic"),
|
||||
("HEMOLYTIC_ACUTE", "Acute Hemolytic"),
|
||||
("HEMOLYTIC_DELAYED", "Delayed Hemolytic"),
|
||||
("ANAPHYLACTIC", "Anaphylactic"),
|
||||
("SEPTIC", "Septic"),
|
||||
("CIRCULATORY_OVERLOAD", "Circulatory Overload"),
|
||||
("LUNG_INJURY", "Transfusion-Related Acute Lung Injury"),
|
||||
("OTHER", "Other"),
|
||||
],
|
||||
max_length=30,
|
||||
),
|
||||
@ -480,10 +484,10 @@ class Migration(migrations.Migration):
|
||||
"severity",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("mild", "Mild"),
|
||||
("moderate", "Moderate"),
|
||||
("severe", "Severe"),
|
||||
("life_threatening", "Life Threatening"),
|
||||
("MILD", "Mild"),
|
||||
("MODERATE", "Moderate"),
|
||||
("SEVERE", "Severe"),
|
||||
("LIFE_THREATENING", "Life Threatening"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
@ -611,11 +615,11 @@ class Migration(migrations.Migration):
|
||||
"urgency",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("routine", "Routine"),
|
||||
("urgent", "Urgent"),
|
||||
("emergency", "Emergency"),
|
||||
("ROUTINE", "Routine"),
|
||||
("URGENT", "Urgent"),
|
||||
("EMERGENCY", "Emergency"),
|
||||
],
|
||||
default="routine",
|
||||
default="ROUTINE",
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
@ -627,14 +631,14 @@ class Migration(migrations.Migration):
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("pending", "Pending"),
|
||||
("processing", "Processing"),
|
||||
("ready", "Ready"),
|
||||
("issued", "Issued"),
|
||||
("completed", "Completed"),
|
||||
("cancelled", "Cancelled"),
|
||||
("PENDING", "Pending"),
|
||||
("PROCESSING", "Processing"),
|
||||
("READY", "Ready"),
|
||||
("ISSUED", "Issued"),
|
||||
("COMPLETED", "Completed"),
|
||||
("CANCELLED", "Cancelled"),
|
||||
],
|
||||
default="pending",
|
||||
default="PENDING",
|
||||
max_length=15,
|
||||
),
|
||||
),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@ -1,275 +0,0 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 16:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("blood_bank", "0002_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="adversereaction",
|
||||
name="reaction_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("FEBRILE", "Febrile Non-Hemolytic"),
|
||||
("ALLERGIC", "Allergic"),
|
||||
("HEMOLYTIC_ACUTE", "Acute Hemolytic"),
|
||||
("HEMOLYTIC_DELAYED", "Delayed Hemolytic"),
|
||||
("ANAPHYLACTIC", "Anaphylactic"),
|
||||
("SEPTIC", "Septic"),
|
||||
("CIRCULATORY_OVERLOAD", "Circulatory Overload"),
|
||||
("LUNG_INJURY", "Transfusion-Related Acute Lung Injury"),
|
||||
("OTHER", "Other"),
|
||||
],
|
||||
max_length=30,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="adversereaction",
|
||||
name="severity",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("MILD", "Mild"),
|
||||
("MODERATE", "Moderate"),
|
||||
("SEVERE", "Severe"),
|
||||
("LIFE_THREATENING", "Life Threatening"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="bloodcomponent",
|
||||
name="name",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("WHOLE_BLOOD", "Whole Blood"),
|
||||
("PACKED_RBC", "Packed Red Blood Cells"),
|
||||
("FRESH_FROZEN_PLASMA", "Fresh Frozen Plasma"),
|
||||
("PLATELETS", "Platelets"),
|
||||
("CRYOPRECIPITATE", "Cryoprecipitate"),
|
||||
("GRANULOCYTES", "Granulocytes"),
|
||||
],
|
||||
max_length=50,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="bloodrequest",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("PENDING", "Pending"),
|
||||
("PROCESSING", "Processing"),
|
||||
("READY", "Ready"),
|
||||
("ISSUED", "Issued"),
|
||||
("COMPLETED", "Completed"),
|
||||
("CANCELLED", "Cancelled"),
|
||||
],
|
||||
default="PENDING",
|
||||
max_length=15,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="bloodrequest",
|
||||
name="urgency",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("ROUTINE", "Routine"),
|
||||
("URGENT", "Urgent"),
|
||||
("EMERGENCY", "Emergency"),
|
||||
],
|
||||
default="ROUTINE",
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="bloodtest",
|
||||
name="result",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("POSITIVE", "Positive"),
|
||||
("NEGATIVE", "Negative"),
|
||||
("INDETERMINATE", "Indeterminate"),
|
||||
("PENDING", "Pending"),
|
||||
],
|
||||
default="PENDING",
|
||||
max_length=15,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="bloodtest",
|
||||
name="test_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("ABO_RH", "ABO/Rh Typing"),
|
||||
("ANTIBODY_SCREEN", "Antibody Screening"),
|
||||
("HIV", "HIV"),
|
||||
("HBV", "Hepatitis B"),
|
||||
("HCV", "Hepatitis C"),
|
||||
("SYPHILIS", "Syphilis"),
|
||||
("HTLV", "HTLV"),
|
||||
("CMV", "CMV"),
|
||||
("MALARIA", "Malaria"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="bloodunit",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("COLLECTED", "Collected"),
|
||||
("TESTING", "Testing"),
|
||||
("QUARANTINE", "Quarantine"),
|
||||
("AVAILABLE", "Available"),
|
||||
("RESERVED", "Reserved"),
|
||||
("ISSUED", "Issued"),
|
||||
("TRANSFUSED", "Transfused"),
|
||||
("EXPIRED", "Expired"),
|
||||
("DISCARDED", "Discarded"),
|
||||
],
|
||||
default="COLLECTED",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="crossmatch",
|
||||
name="compatibility",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("COMPATIBLE", "Compatible"),
|
||||
("INCOMPATIBLE", "Incompatible"),
|
||||
("PENDING", "Pending"),
|
||||
],
|
||||
default="PENDING",
|
||||
max_length=15,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="crossmatch",
|
||||
name="test_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("MAJOR", "Major Crossmatch"),
|
||||
("MINOR", "Minor Crossmatch"),
|
||||
("IMMEDIATE_SPIN", "Immediate Spin"),
|
||||
("ANTIGLOBULIN", "Antiglobulin Test"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="donor",
|
||||
name="donor_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("VOLUNTARY", "Voluntary"),
|
||||
("REPLACEMENT", "Replacement"),
|
||||
("AUTOLOGOUS", "Autologous"),
|
||||
("DIRECTED", "Directed"),
|
||||
],
|
||||
default="VOLUNTARY",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="donor",
|
||||
name="gender",
|
||||
field=models.CharField(
|
||||
choices=[("MALE", "Male"), ("FEMALE", "Female"), ("OTHER", "Other")],
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="donor",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("ACTIVE", "Active"),
|
||||
("DEFERRED", "Deferred"),
|
||||
("PERMANENTLY_DEFERRED", "Permanently Deferred"),
|
||||
("INACTIVE", "Inactive"),
|
||||
],
|
||||
default="ACTIVE",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="inventorylocation",
|
||||
name="location_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("REFRIGERATOR", "Refrigerator"),
|
||||
("FREEZER", "Freezer"),
|
||||
("PLATELET_AGITATOR", "Platelet Agitator"),
|
||||
("QUARANTINE", "Quarantine"),
|
||||
("TESTING", "Testing Area"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="qualitycontrol",
|
||||
name="capa_priority",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[("LOW", "Low"), ("MEDIUM", "Medium"), ("HIGH", "High")],
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="qualitycontrol",
|
||||
name="capa_status",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("OPEN", "Open"),
|
||||
("IN_PROGRESS", "In Progress"),
|
||||
("CLOSED", "Closed"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="qualitycontrol",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
choices=[("PASS", "Pass"), ("FAIL", "Fail"), ("PENDING", "Pending")],
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="qualitycontrol",
|
||||
name="test_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("TEMPERATURE_MONITORING", "Temperature Monitoring"),
|
||||
("EQUIPMENT_CALIBRATION", "Equipment Calibration"),
|
||||
("REAGENT_TESTING", "Reagent Testing"),
|
||||
("PROFICIENCY_TESTING", "Proficiency Testing"),
|
||||
("PROCESS_VALIDATION", "Process Validation"),
|
||||
],
|
||||
max_length=30,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="transfusion",
|
||||
name="status",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("STARTED", "Started"),
|
||||
("IN_PROGRESS", "In Progress"),
|
||||
("COMPLETED", "Completed"),
|
||||
("STOPPED", "Stopped"),
|
||||
("ADVERSE_REACTION", "Adverse Reaction"),
|
||||
],
|
||||
default="STARTED",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,11 +1,11 @@
|
||||
from django.db import models
|
||||
# from django.contrib.auth.models import User
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from hr.models import Department
|
||||
from patients.models import PatientProfile
|
||||
from accounts.models import User
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class BloodGroup(models.Model):
|
||||
"""Blood group types (A, B, AB, O) with Rh factor"""
|
||||
@ -71,7 +71,7 @@ class Donor(models.Model):
|
||||
weight = models.FloatField(validators=[MinValueValidator(45.0)]) # Minimum weight for donation
|
||||
height = models.FloatField(validators=[MinValueValidator(140.0)]) # In cm
|
||||
notes = models.TextField(blank=True)
|
||||
created_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='created_donors')
|
||||
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='created_donors')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
@ -164,7 +164,7 @@ class BloodUnit(models.Model):
|
||||
bag_type = models.CharField(max_length=50)
|
||||
anticoagulant = models.CharField(max_length=50, default='CPDA-1')
|
||||
collection_site = models.CharField(max_length=100)
|
||||
collected_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='collected_units')
|
||||
collected_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='collected_units')
|
||||
notes = models.TextField(blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
@ -213,11 +213,11 @@ class BloodTest(models.Model):
|
||||
test_type = models.CharField(max_length=20, choices=TestType.choices)
|
||||
result = models.CharField(max_length=15, choices=TestResult.choices, default=TestResult.PENDING)
|
||||
test_date = models.DateTimeField()
|
||||
tested_by = models.ForeignKey(User, on_delete=models.PROTECT)
|
||||
tested_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||
equipment_used = models.CharField(max_length=100, blank=True)
|
||||
lot_number = models.CharField(max_length=50, blank=True)
|
||||
notes = models.TextField(blank=True)
|
||||
verified_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='verified_tests', null=True,
|
||||
verified_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='verified_tests', null=True,
|
||||
blank=True)
|
||||
verified_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
@ -248,11 +248,11 @@ class CrossMatch(models.Model):
|
||||
test_type = models.CharField(max_length=20, choices=CrossmatchTestType.choices)
|
||||
compatibility = models.CharField(max_length=15, choices=CompatibilityStatus.choices, default=CompatibilityStatus.PENDING)
|
||||
test_date = models.DateTimeField()
|
||||
tested_by = models.ForeignKey(User, on_delete=models.PROTECT)
|
||||
tested_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||
temperature = models.CharField(max_length=20, default='37°C')
|
||||
incubation_time = models.PositiveIntegerField(default=15) # minutes
|
||||
notes = models.TextField(blank=True)
|
||||
verified_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='verified_crossmatches', null=True,
|
||||
verified_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='verified_crossmatches', null=True,
|
||||
blank=True)
|
||||
verified_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
@ -281,7 +281,7 @@ class BloodRequest(models.Model):
|
||||
request_number = models.CharField(max_length=20, unique=True)
|
||||
patient = models.ForeignKey(PatientProfile, on_delete=models.PROTECT, related_name='blood_requests')
|
||||
requesting_department = models.ForeignKey(Department, on_delete=models.PROTECT)
|
||||
requesting_physician = models.ForeignKey(User, on_delete=models.PROTECT, related_name='blood_requests')
|
||||
requesting_physician = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='blood_requests')
|
||||
component_requested = models.ForeignKey(BloodComponent, on_delete=models.PROTECT)
|
||||
units_requested = models.PositiveIntegerField(validators=[MinValueValidator(1)])
|
||||
urgency = models.CharField(max_length=10, choices=UrgencyLevel.choices, default=UrgencyLevel.ROUTINE)
|
||||
@ -293,14 +293,14 @@ class BloodRequest(models.Model):
|
||||
status = models.CharField(max_length=15, choices=RequestStatus.choices, default=RequestStatus.PENDING)
|
||||
request_date = models.DateTimeField(auto_now_add=True)
|
||||
required_by = models.DateTimeField()
|
||||
processed_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='processed_requests', null=True,
|
||||
processed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='processed_requests', null=True,
|
||||
blank=True)
|
||||
processed_at = models.DateTimeField(null=True, blank=True)
|
||||
notes = models.TextField(blank=True)
|
||||
|
||||
# Cancellation fields
|
||||
cancellation_reason = models.TextField(blank=True)
|
||||
cancelled_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='cancelled_requests', null=True,
|
||||
cancelled_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='cancelled_requests', null=True,
|
||||
blank=True)
|
||||
cancellation_date = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
@ -320,8 +320,8 @@ class BloodIssue(models.Model):
|
||||
blood_request = models.ForeignKey(BloodRequest, on_delete=models.PROTECT, related_name='issues')
|
||||
blood_unit = models.OneToOneField(BloodUnit, on_delete=models.PROTECT, related_name='issue')
|
||||
crossmatch = models.ForeignKey(CrossMatch, on_delete=models.PROTECT, null=True, blank=True)
|
||||
issued_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='issued_units')
|
||||
issued_to = models.ForeignKey(User, on_delete=models.PROTECT, related_name='received_units') # Nurse/physician
|
||||
issued_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='issued_units')
|
||||
issued_to = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='received_units') # Nurse/physician
|
||||
issue_date = models.DateTimeField(auto_now_add=True)
|
||||
expiry_time = models.DateTimeField() # 4 hours from issue for RBC
|
||||
returned = models.BooleanField(default=False)
|
||||
@ -356,8 +356,8 @@ class Transfusion(models.Model):
|
||||
status = models.CharField(max_length=20, choices=TransfusionStatus.choices, default=TransfusionStatus.STARTED)
|
||||
volume_transfused = models.PositiveIntegerField(null=True, blank=True) # ml
|
||||
transfusion_rate = models.CharField(max_length=50, blank=True) # ml/hour
|
||||
administered_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='administered_transfusions')
|
||||
witnessed_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='witnessed_transfusions', null=True,
|
||||
administered_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='administered_transfusions')
|
||||
witnessed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='witnessed_transfusions', null=True,
|
||||
blank=True)
|
||||
pre_transfusion_vitals = models.JSONField(default=dict) # BP, HR, Temp, etc.
|
||||
post_transfusion_vitals = models.JSONField(default=dict)
|
||||
@ -374,9 +374,9 @@ class Transfusion(models.Model):
|
||||
|
||||
# Completion/Stop fields
|
||||
stop_reason = models.TextField(blank=True)
|
||||
stopped_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='stopped_transfusions', null=True,
|
||||
stopped_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='stopped_transfusions', null=True,
|
||||
blank=True)
|
||||
completed_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='completed_transfusions', null=True,
|
||||
completed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='completed_transfusions', null=True,
|
||||
blank=True)
|
||||
completion_notes = models.TextField(blank=True)
|
||||
|
||||
@ -420,8 +420,8 @@ class AdverseReaction(models.Model):
|
||||
symptoms = models.TextField()
|
||||
treatment_given = models.TextField()
|
||||
outcome = models.TextField()
|
||||
reported_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='reported_reactions')
|
||||
investigated_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='investigated_reactions',
|
||||
reported_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='reported_reactions')
|
||||
investigated_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='investigated_reactions',
|
||||
null=True, blank=True)
|
||||
investigation_notes = models.TextField(blank=True)
|
||||
regulatory_reported = models.BooleanField(default=False)
|
||||
@ -488,8 +488,8 @@ class QualityControl(models.Model):
|
||||
expected_results = models.TextField()
|
||||
actual_results = models.TextField()
|
||||
status = models.CharField(max_length=10, choices=QualityTestStatus.choices)
|
||||
performed_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='qc_tests')
|
||||
reviewed_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='reviewed_qc_tests', null=True,
|
||||
performed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='qc_tests')
|
||||
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='reviewed_qc_tests', null=True,
|
||||
blank=True)
|
||||
review_date = models.DateTimeField(null=True, blank=True)
|
||||
review_notes = models.TextField(blank=True)
|
||||
@ -501,7 +501,7 @@ class QualityControl(models.Model):
|
||||
capa_number = models.CharField(max_length=50, blank=True)
|
||||
capa_priority = models.CharField(max_length=10, choices=[('LOW', 'Low'), ('MEDIUM', 'Medium'), ('HIGH', 'High')],
|
||||
blank=True)
|
||||
capa_initiated_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='initiated_capas', null=True,
|
||||
capa_initiated_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='initiated_capas', null=True,
|
||||
blank=True)
|
||||
capa_date = models.DateTimeField(null=True, blank=True)
|
||||
capa_assessment = models.TextField(blank=True)
|
||||
|
||||
BIN
catherine_perry.pdf
Normal file
BIN
catherine_perry.pdf
Normal file
Binary file not shown.
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -6,10 +6,10 @@ import uuid
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import models
|
||||
from accounts.models import User
|
||||
from django.utils import timezone
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from decimal import Decimal
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
@ -83,7 +83,7 @@ class Message(models.Model):
|
||||
|
||||
# Sender and recipients
|
||||
sender = models.ForeignKey(
|
||||
User,
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='sent_messages',
|
||||
help_text="Message sender"
|
||||
@ -249,7 +249,7 @@ class MessageRecipient(models.Model):
|
||||
help_text="Type of recipient"
|
||||
)
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
@ -472,7 +472,7 @@ class NotificationTemplate(models.Model):
|
||||
help_text="Last update timestamp"
|
||||
)
|
||||
created_by = models.ForeignKey(
|
||||
User,
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
@ -584,7 +584,7 @@ class AlertRule(models.Model):
|
||||
|
||||
# Recipients
|
||||
default_recipients = models.ManyToManyField(
|
||||
User,
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank=True,
|
||||
help_text="Default alert recipients"
|
||||
)
|
||||
@ -629,7 +629,7 @@ class AlertRule(models.Model):
|
||||
help_text="Last update timestamp"
|
||||
)
|
||||
created_by = models.ForeignKey(
|
||||
User,
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
@ -743,7 +743,7 @@ class AlertInstance(models.Model):
|
||||
|
||||
# Response tracking
|
||||
acknowledged_by = models.ForeignKey(
|
||||
User,
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
@ -751,7 +751,7 @@ class AlertInstance(models.Model):
|
||||
help_text="User who acknowledged the alert"
|
||||
)
|
||||
resolved_by = models.ForeignKey(
|
||||
User,
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
@ -938,7 +938,7 @@ class CommunicationChannel(models.Model):
|
||||
help_text="Last update timestamp"
|
||||
)
|
||||
created_by = models.ForeignKey(
|
||||
User,
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-15 14:05
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
|
||||
Binary file not shown.
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
emr/__pycache__/utils.cpython-312.pyc
Normal file
BIN
emr/__pycache__/utils.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -7,10 +7,12 @@ from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from .models import (
|
||||
Encounter, VitalSigns, ProblemList, CarePlan,
|
||||
ClinicalNote, NoteTemplate
|
||||
Encounter, VitalSigns, ProblemList, CarePlan,
|
||||
ClinicalNote, NoteTemplate, Icd10
|
||||
)
|
||||
|
||||
admin.site.register(Icd10)
|
||||
|
||||
|
||||
@admin.register(Encounter)
|
||||
class EncounterAdmin(admin.ModelAdmin):
|
||||
@ -576,3 +578,5 @@ class ProblemListInline(admin.TabularInline):
|
||||
# Add inlines to Encounter admin
|
||||
EncounterAdmin.inlines = [VitalSignsInline, ClinicalNoteInline, ProblemListInline]
|
||||
|
||||
|
||||
|
||||
|
||||
77
emr/forms.py
77
emr/forms.py
@ -210,6 +210,18 @@ class CarePlanForm(forms.ModelForm):
|
||||
'approved', 'approved_by'
|
||||
]
|
||||
widgets = {
|
||||
'plan_type': forms.Select(attrs={'class': 'form-control form-control-sm'}),
|
||||
'category': forms.Select(attrs={'class': 'form-control form-control-sm'}),
|
||||
'patient': forms.Select(attrs={'class': 'form-control form-control-sm'}),
|
||||
'title': forms.TextInput(attrs={'class': 'form-control form-control-sm'}),
|
||||
'priority': forms.Select(attrs={'class': 'form-control form-control-sm'}),
|
||||
'status': forms.Select(attrs={'class': 'form-control form-control-sm'}),
|
||||
'primary_provider': forms.Select(attrs={'class': 'form-control form-control-sm'}),
|
||||
'care_team': forms.Select(attrs={'class': 'form-control form-control-sm'}),
|
||||
'completion_percentage': forms.NumberInput(attrs={'class': 'form-control form-control-sm'}),
|
||||
'approved': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'approved_by': forms.Select(attrs={'class': 'form-control form-control-sm'}),
|
||||
'related_problems': forms.SelectMultiple(attrs={'class': 'form-control form-control-sm'}),
|
||||
'start_date': forms.DateInput(attrs={'type': 'date', 'class':'form-control form control-sm'}),
|
||||
'end_date': forms.DateInput(attrs={'type': 'date', 'class':'form-control form control-sm'}),
|
||||
'target_completion_date': forms.DateInput(attrs={'type': 'date', 'class':'form-control form control-sm'}),
|
||||
@ -270,6 +282,71 @@ class CarePlanForm(forms.ModelForm):
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class CarePlanProgressForm(forms.ModelForm):
|
||||
"""
|
||||
Restricted form used to update progress on an existing care plan
|
||||
without touching identity/scope fields (patient, goals, etc.).
|
||||
"""
|
||||
class Meta:
|
||||
model = CarePlan
|
||||
fields = [
|
||||
'status',
|
||||
'completion_percentage',
|
||||
'progress_notes',
|
||||
'outcomes_achieved',
|
||||
'evaluation_criteria',
|
||||
'monitoring_parameters',
|
||||
'last_reviewed',
|
||||
'next_review_date',
|
||||
]
|
||||
widgets = {
|
||||
'status': forms.Select(attrs={'class': 'form-control form-control-sm'}),
|
||||
'completion_percentage': forms.NumberInput(attrs={
|
||||
'class': 'form-control form-control-sm',
|
||||
'min': 0, 'max': 100, 'step': 1,
|
||||
}),
|
||||
'progress_notes': forms.Textarea(attrs={'rows': 4, 'class': 'form-control form-control-sm'}),
|
||||
'outcomes_achieved': forms.Textarea(attrs={'rows': 3, 'class': 'form-control form-control-sm'}),
|
||||
'evaluation_criteria': forms.Textarea(attrs={'rows': 3, 'class': 'form-control form-control-sm'}),
|
||||
'monitoring_parameters': forms.Textarea(attrs={'rows': 3, 'class': 'form-control form-control-sm'}),
|
||||
'last_reviewed': forms.DateInput(attrs={'type': 'date', 'class': 'form-control form-control-sm'}),
|
||||
'next_review_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control form-control-sm'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Accept tenant for potential future use; not strictly needed here.
|
||||
self.tenant = kwargs.pop('tenant', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Sensible default: prefill last_reviewed with today if empty
|
||||
if not self.instance.pk and not self.fields['last_reviewed'].initial:
|
||||
self.fields['last_reviewed'].initial = timezone.now().date()
|
||||
|
||||
def clean(self):
|
||||
cleaned = super().clean()
|
||||
status = cleaned.get('status')
|
||||
pct = cleaned.get('completion_percentage')
|
||||
|
||||
# Range check
|
||||
if pct is not None and not (0 <= pct <= 100):
|
||||
self.add_error('completion_percentage', 'Completion must be between 0 and 100.')
|
||||
|
||||
# Status / % consistency
|
||||
if status == 'COMPLETED' and (pct is None or pct < 100):
|
||||
self.add_error('completion_percentage', 'Completion must be 100% when status is COMPLETED.')
|
||||
|
||||
if status == 'DRAFT' and pct and pct > 0:
|
||||
self.add_error('completion_percentage', 'Draft care plans must have 0% completion.')
|
||||
|
||||
# Dates sanity: next_review_date should not be before last_reviewed
|
||||
last_rev = cleaned.get('last_reviewed')
|
||||
next_rev = cleaned.get('next_review_date')
|
||||
if last_rev and next_rev and next_rev < last_rev:
|
||||
self.add_error('next_review_date', 'Next review date must be on/after Last reviewed.')
|
||||
|
||||
return cleaned
|
||||
|
||||
|
||||
class ClinicalNoteForm(forms.ModelForm):
|
||||
"""
|
||||
Form for creating and updating clinical notes.
|
||||
|
||||
0
emr/management/__init__.py
Normal file
0
emr/management/__init__.py
Normal file
BIN
emr/management/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
emr/management/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
0
emr/management/commands/__init__.py
Normal file
0
emr/management/commands/__init__.py
Normal file
BIN
emr/management/commands/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
emr/management/commands/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
emr/management/commands/__pycache__/import_icd10.cpython-312.pyc
Normal file
BIN
emr/management/commands/__pycache__/import_icd10.cpython-312.pyc
Normal file
Binary file not shown.
174
emr/management/commands/import_icd10.py
Normal file
174
emr/management/commands/import_icd10.py
Normal file
@ -0,0 +1,174 @@
|
||||
# myapp/management/commands/import_icd10.py
|
||||
import xmlschema
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
from emr.models import Icd10
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Import ICD-10-CM tabular XML into Icd10 model (auto-detects root)."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--xsd", required=True, help="Path to icd10cm-tabular-2026.xsd")
|
||||
parser.add_argument("--xml", required=True, help="Path to icd10cm-tabular-2026.xml")
|
||||
parser.add_argument("--truncate", action="store_true", help="Delete existing Icd10 rows first")
|
||||
|
||||
# ------------------------ helpers ------------------------
|
||||
|
||||
def _as_text(self, val):
|
||||
if val is None:
|
||||
return None
|
||||
if isinstance(val, dict):
|
||||
# xmlschema may convert text/attributes into #text/@value/etc.
|
||||
return val.get("#text") or val.get("@value") or val.get("value") or str(val)
|
||||
return str(val)
|
||||
|
||||
def _ensure_list(self, maybe_list):
|
||||
if maybe_list is None:
|
||||
return []
|
||||
if isinstance(maybe_list, list):
|
||||
return maybe_list
|
||||
return [maybe_list]
|
||||
|
||||
def _find_first_with_key(self, data, key):
|
||||
"""Depth-first search: return the first dict that directly contains `key`."""
|
||||
if isinstance(data, dict):
|
||||
if key in data:
|
||||
return data
|
||||
for v in data.values():
|
||||
found = self._find_first_with_key(v, key)
|
||||
if found is not None:
|
||||
return found
|
||||
elif isinstance(data, list):
|
||||
for item in data:
|
||||
found = self._find_first_with_key(item, key)
|
||||
if found is not None:
|
||||
return found
|
||||
return None
|
||||
|
||||
def _collect_rows(self, chapters):
|
||||
"""
|
||||
Build Icd10 rows + parent links from a chapters dict/list.
|
||||
Expected minimal structure:
|
||||
chapter -> section? -> diag (recursive)
|
||||
"""
|
||||
rows = []
|
||||
parent_links = []
|
||||
|
||||
def import_diag(diag, chapter_name, section_name, parent_code=None):
|
||||
code = self._as_text(diag.get("name"))
|
||||
desc = self._as_text(diag.get("desc"))
|
||||
if not code:
|
||||
return
|
||||
|
||||
children = self._ensure_list(diag.get("diag"))
|
||||
is_header = bool(children) and not (desc and desc.strip())
|
||||
|
||||
rows.append(Icd10(
|
||||
code=code,
|
||||
description=desc,
|
||||
chapter_name=self._as_text(chapter_name),
|
||||
section_name=self._as_text(section_name),
|
||||
parent=None, # set later
|
||||
is_header=is_header,
|
||||
))
|
||||
if parent_code:
|
||||
parent_links.append((code, parent_code))
|
||||
|
||||
for child in children:
|
||||
import_diag(child, chapter_name, section_name, parent_code=code)
|
||||
|
||||
# Normalize chapters to a list
|
||||
chapters = self._ensure_list(chapters)
|
||||
for ch in chapters:
|
||||
ch_name = self._as_text(ch.get("name"))
|
||||
|
||||
# Sections may be missing in some packs; diags may be directly under chapter
|
||||
sections = self._ensure_list(ch.get("section"))
|
||||
if sections:
|
||||
for sec in sections:
|
||||
sec_name = self._as_text(sec.get("name"))
|
||||
for d in self._ensure_list(sec.get("diag")):
|
||||
import_diag(d, ch_name, sec_name, parent_code=None)
|
||||
else:
|
||||
# If no sections, look for diags at chapter level
|
||||
for d in self._ensure_list(ch.get("diag")):
|
||||
import_diag(d, ch_name, None, parent_code=None)
|
||||
|
||||
return rows, parent_links
|
||||
|
||||
def handle(self, *args, **opts):
|
||||
xsd_path = opts["xsd"]
|
||||
xml_path = opts["xml"]
|
||||
|
||||
try:
|
||||
xs = xmlschema.XMLSchema(xsd_path)
|
||||
except Exception as e:
|
||||
raise CommandError(f"Failed to load XSD: {e}")
|
||||
|
||||
try:
|
||||
# to_dict() already flattens namespaces into keys; we’ll auto-detect paths.
|
||||
data = xs.to_dict(xml_path)
|
||||
except Exception as e:
|
||||
raise CommandError(f"Failed to parse XML: {e}")
|
||||
|
||||
# If the root is a single-key dict, unwrap while keeping reference
|
||||
if isinstance(data, dict) and len(data) == 1:
|
||||
root_key, root_val = next(iter(data.items()))
|
||||
root = root_val
|
||||
else:
|
||||
root = data
|
||||
|
||||
# Find the dict that *contains* the "chapter" key (any depth)
|
||||
container_with_chapter = self._find_first_with_key(root, "chapter")
|
||||
if not container_with_chapter:
|
||||
# Fall back: sometimes structure uses "chapters"
|
||||
container_with_chapter = self._find_first_with_key(root, "chapters")
|
||||
if container_with_chapter and isinstance(container_with_chapter["chapters"], dict):
|
||||
# Normalize "chapters" -> "chapter" if it’s nested like {"chapters": {"chapter": [...]}}
|
||||
if "chapter" in container_with_chapter["chapters"]:
|
||||
container_with_chapter = container_with_chapter["chapters"]
|
||||
|
||||
if not container_with_chapter or ("chapter" not in container_with_chapter):
|
||||
# Give user a quick peek at available top-level keys to debug
|
||||
preview_keys = list(root.keys()) if isinstance(root, dict) else type(root)
|
||||
raise CommandError(
|
||||
"Could not locate 'chapter' anywhere in the parsed XML. "
|
||||
f"Top-level preview: {preview_keys}"
|
||||
)
|
||||
|
||||
chapters = container_with_chapter.get("chapter")
|
||||
if chapters is None:
|
||||
raise CommandError("Found container for chapters, but 'chapter' is empty.")
|
||||
|
||||
# Optionally truncate
|
||||
if opts["truncate"]:
|
||||
self.stdout.write(self.style.WARNING("Truncating existing Icd10 data..."))
|
||||
Icd10.objects.all().delete()
|
||||
|
||||
# Collect rows + parent links
|
||||
self.stdout.write("Collecting ICD-10 rows...")
|
||||
rows, parent_links = self._collect_rows(chapters)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Collected {len(rows)} rows. Inserting..."))
|
||||
|
||||
BATCH = 1000
|
||||
with transaction.atomic():
|
||||
for i in range(0, len(rows), BATCH):
|
||||
Icd10.objects.bulk_create(rows[i:i+BATCH], ignore_conflicts=True)
|
||||
|
||||
# Link parents
|
||||
if parent_links:
|
||||
self.stdout.write("Linking parents...")
|
||||
code_to_obj = {o.code: o for o in Icd10.objects.only("id", "code")}
|
||||
updates = []
|
||||
for child_code, parent_code in parent_links:
|
||||
child = code_to_obj.get(child_code)
|
||||
parent = code_to_obj.get(parent_code)
|
||||
if child and parent and child.parent_id != parent.id:
|
||||
child.parent_id = parent.id
|
||||
updates.append(child)
|
||||
for i in range(0, len(updates), BATCH):
|
||||
Icd10.objects.bulk_update(updates[i:i+BATCH], ["parent"])
|
||||
self.stdout.write(self.style.SUCCESS(f"Linked {len(updates)} parent relations."))
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("ICD-10 import completed successfully."))
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,24 +0,0 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-16 00:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("emr", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="vitalsigns",
|
||||
name="temperature",
|
||||
field=models.DecimalField(
|
||||
blank=True,
|
||||
decimal_places=1,
|
||||
help_text="Temperature in Celsius",
|
||||
max_digits=4,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
||||
468
emr/migrations/0002_initial.py
Normal file
468
emr/migrations/0002_initial.py
Normal file
@ -0,0 +1,468 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("appointments", "0002_initial"),
|
||||
("core", "0001_initial"),
|
||||
("emr", "0001_initial"),
|
||||
("inpatients", "0001_initial"),
|
||||
("patients", "0001_initial"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="encounter",
|
||||
name="admission",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Related admission",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="encounters",
|
||||
to="inpatients.admission",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="encounter",
|
||||
name="appointment",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Related appointment",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="encounters",
|
||||
to="appointments.appointmentrequest",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="encounter",
|
||||
name="created_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="User who created the encounter",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="created_encounters",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="encounter",
|
||||
name="patient",
|
||||
field=models.ForeignKey(
|
||||
help_text="Patient for this encounter",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="encounters",
|
||||
to="patients.patientprofile",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="encounter",
|
||||
name="provider",
|
||||
field=models.ForeignKey(
|
||||
help_text="Primary provider for this encounter",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="encounters",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="encounter",
|
||||
name="signed_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Provider who signed off",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="signed_encounters",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="encounter",
|
||||
name="tenant",
|
||||
field=models.ForeignKey(
|
||||
help_text="Organization tenant",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="encounters",
|
||||
to="core.tenant",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="clinicalnote",
|
||||
name="encounter",
|
||||
field=models.ForeignKey(
|
||||
help_text="Associated encounter",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="clinical_notes",
|
||||
to="emr.encounter",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="icd10",
|
||||
name="parent",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="children",
|
||||
to="emr.icd10",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notetemplate",
|
||||
name="created_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="User who created the template",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="created_note_templates",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notetemplate",
|
||||
name="previous_version",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Previous version of this template",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="newer_versions",
|
||||
to="emr.notetemplate",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notetemplate",
|
||||
name="tenant",
|
||||
field=models.ForeignKey(
|
||||
help_text="Organization tenant",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="note_templates",
|
||||
to="core.tenant",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="clinicalnote",
|
||||
name="template",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Template used for this note",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="notes",
|
||||
to="emr.notetemplate",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="problemlist",
|
||||
name="created_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="User who created the problem",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="created_problems",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="problemlist",
|
||||
name="diagnosing_provider",
|
||||
field=models.ForeignKey(
|
||||
help_text="Provider who diagnosed the problem",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="diagnosed_problems",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="problemlist",
|
||||
name="managing_provider",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Provider managing the problem",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="managed_problems",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="problemlist",
|
||||
name="patient",
|
||||
field=models.ForeignKey(
|
||||
help_text="Patient",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="problems",
|
||||
to="patients.patientprofile",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="problemlist",
|
||||
name="related_encounter",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Encounter where problem was identified",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="problems_identified",
|
||||
to="emr.encounter",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="problemlist",
|
||||
name="tenant",
|
||||
field=models.ForeignKey(
|
||||
help_text="Organization tenant",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="problem_lists",
|
||||
to="core.tenant",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="problemlist",
|
||||
name="verified_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Provider who verified the problem",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="verified_problems",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="clinicalnote",
|
||||
name="related_problems",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Related problems",
|
||||
related_name="related_clinical_notes",
|
||||
to="emr.problemlist",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="careplan",
|
||||
name="related_problems",
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text="Related problems addressed by this plan",
|
||||
related_name="care_plans",
|
||||
to="emr.problemlist",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="vitalsigns",
|
||||
name="encounter",
|
||||
field=models.ForeignKey(
|
||||
help_text="Associated encounter",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="vital_signs",
|
||||
to="emr.encounter",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="vitalsigns",
|
||||
name="measured_by",
|
||||
field=models.ForeignKey(
|
||||
help_text="Staff member who took measurements",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="vital_signs_measurements",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="vitalsigns",
|
||||
name="patient",
|
||||
field=models.ForeignKey(
|
||||
help_text="Patient",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="vital_signs",
|
||||
to="patients.patientprofile",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="vitalsigns",
|
||||
name="verified_by",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Staff member who verified measurements",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="verified_vital_signs",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="encounter",
|
||||
index=models.Index(
|
||||
fields=["tenant", "status"], name="emr_encount_tenant__6734f5_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="encounter",
|
||||
index=models.Index(
|
||||
fields=["patient", "start_datetime"],
|
||||
name="emr_encount_patient_c0f443_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="encounter",
|
||||
index=models.Index(
|
||||
fields=["provider"], name="emr_encount_provide_5e06b3_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="encounter",
|
||||
index=models.Index(
|
||||
fields=["encounter_type"], name="emr_encount_encount_019f80_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="encounter",
|
||||
index=models.Index(
|
||||
fields=["start_datetime"], name="emr_encount_start_d_a01018_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="notetemplate",
|
||||
index=models.Index(
|
||||
fields=["tenant", "is_active"], name="emr_note_te_tenant__caa5d3_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="notetemplate",
|
||||
index=models.Index(
|
||||
fields=["note_type", "specialty"], name="emr_note_te_note_ty_d18594_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="notetemplate",
|
||||
index=models.Index(
|
||||
fields=["is_default"], name="emr_note_te_is_defa_7b5223_idx"
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="notetemplate",
|
||||
unique_together={("tenant", "note_type", "specialty", "is_default")},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="problemlist",
|
||||
index=models.Index(
|
||||
fields=["tenant", "status"], name="emr_problem_tenant__bb0abf_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="problemlist",
|
||||
index=models.Index(
|
||||
fields=["patient", "status"], name="emr_problem_patient_d732d2_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="problemlist",
|
||||
index=models.Index(
|
||||
fields=["problem_type"], name="emr_problem_problem_90c9f8_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="problemlist",
|
||||
index=models.Index(
|
||||
fields=["priority"], name="emr_problem_priorit_327dd3_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="problemlist",
|
||||
index=models.Index(
|
||||
fields=["onset_date"], name="emr_problem_onset_d_de94bd_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="clinicalnote",
|
||||
index=models.Index(
|
||||
fields=["patient", "note_datetime"],
|
||||
name="emr_clinica_patient_442718_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="clinicalnote",
|
||||
index=models.Index(
|
||||
fields=["encounter"], name="emr_clinica_encount_751749_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="clinicalnote",
|
||||
index=models.Index(
|
||||
fields=["author"], name="emr_clinica_author__85ec13_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="clinicalnote",
|
||||
index=models.Index(
|
||||
fields=["note_type"], name="emr_clinica_note_ty_e6c13c_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="clinicalnote",
|
||||
index=models.Index(fields=["status"], name="emr_clinica_status_0ba513_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="careplan",
|
||||
index=models.Index(
|
||||
fields=["tenant", "status"], name="emr_care_pl_tenant__46659b_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="careplan",
|
||||
index=models.Index(
|
||||
fields=["patient", "status"], name="emr_care_pl_patient_a85a8d_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="careplan",
|
||||
index=models.Index(
|
||||
fields=["primary_provider"], name="emr_care_pl_primary_7b0b7d_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="careplan",
|
||||
index=models.Index(
|
||||
fields=["start_date", "end_date"], name="emr_care_pl_start_d_1183e0_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="careplan",
|
||||
index=models.Index(
|
||||
fields=["priority"], name="emr_care_pl_priorit_0a41d3_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="vitalsigns",
|
||||
index=models.Index(
|
||||
fields=["patient", "measured_datetime"],
|
||||
name="emr_vital_s_patient_0fc206_idx",
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="vitalsigns",
|
||||
index=models.Index(
|
||||
fields=["encounter"], name="emr_vital_s_encount_6b9829_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="vitalsigns",
|
||||
index=models.Index(
|
||||
fields=["measured_datetime"], name="emr_vital_s_measure_8badac_idx"
|
||||
),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
BIN
emr/migrations/__pycache__/0002_initial.cpython-312.pyc
Normal file
BIN
emr/migrations/__pycache__/0002_initial.cpython-312.pyc
Normal file
Binary file not shown.
596
emr/models.py
596
emr/models.py
@ -16,7 +16,53 @@ class Encounter(models.Model):
|
||||
"""
|
||||
Clinical encounter model for tracking patient visits and care episodes.
|
||||
"""
|
||||
|
||||
class EncounterType(models.TextChoices):
|
||||
INPATIENT = 'INPATIENT', 'Inpatient'
|
||||
OUTPATIENT = 'OUTPATIENT', 'Outpatient'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||
URGENT_CARE = 'URGENT_CARE', 'Urgent Care'
|
||||
OBSERVATION = 'OBSERVATION', 'Observation'
|
||||
TELEMEDICINE = 'TELEMEDICINE', 'Telemedicine'
|
||||
HOME_VISIT = 'HOME_VISIT', 'Home Visit'
|
||||
CONSULTATION = 'CONSULTATION', 'Consultation'
|
||||
FOLLOW_UP = 'FOLLOW_UP', 'Follow-up'
|
||||
PROCEDURE = 'PROCEDURE', 'Procedure'
|
||||
SURGERY = 'SURGERY', 'Surgery'
|
||||
DIAGNOSTIC = 'DIAGNOSTIC', 'Diagnostic'
|
||||
PREVENTIVE = 'PREVENTIVE', 'Preventive Care'
|
||||
|
||||
class EncounterClass(models.TextChoices):
|
||||
AMB = 'AMB', 'Ambulatory'
|
||||
EMER = 'EMER', 'Emergency'
|
||||
FLD = 'FLD', 'Field'
|
||||
HH = 'HH', 'Home Health'
|
||||
IMP = 'IMP', 'Inpatient'
|
||||
ACUTE = 'ACUTE', 'Inpatient Acute'
|
||||
NONAC = 'NONAC', 'Inpatient Non-Acute'
|
||||
OBSENC = 'OBSENC', 'Observation Encounter'
|
||||
PRENC = 'PRENC', 'Pre-Admission'
|
||||
SS = 'SS', 'Short Stay'
|
||||
VR = 'VR', 'Virtual'
|
||||
|
||||
class EncounterStatus(models.TextChoices):
|
||||
PLANNED = 'PLANNED', 'Planned'
|
||||
ARRIVED = 'ARRIVED', 'Arrived'
|
||||
TRIAGED = 'TRIAGED', 'Triaged'
|
||||
IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
|
||||
ON_HOLD = 'ON_HOLD', 'On Hold'
|
||||
FINISHED = 'FINISHED', 'Finished'
|
||||
CANCELLED = 'CANCELLED', 'Cancelled'
|
||||
ENTERED_IN_ERROR = 'ENTERED_IN_ERROR', 'Entered in Error'
|
||||
UNKNOWN = 'UNKNOWN', 'Unknown'
|
||||
|
||||
class Priority(models.TextChoices):
|
||||
ROUTINE = 'ROUTINE', 'Routine'
|
||||
URGENT = 'URGENT', 'Urgent'
|
||||
STAT = 'STAT', 'STAT'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||
|
||||
|
||||
|
||||
# Tenant relationship
|
||||
tenant = models.ForeignKey(
|
||||
'core.Tenant',
|
||||
@ -50,40 +96,13 @@ class Encounter(models.Model):
|
||||
# Encounter Details
|
||||
encounter_type = models.CharField(
|
||||
max_length=30,
|
||||
choices=[
|
||||
('INPATIENT', 'Inpatient'),
|
||||
('OUTPATIENT', 'Outpatient'),
|
||||
('EMERGENCY', 'Emergency'),
|
||||
('URGENT_CARE', 'Urgent Care'),
|
||||
('OBSERVATION', 'Observation'),
|
||||
('TELEMEDICINE', 'Telemedicine'),
|
||||
('HOME_VISIT', 'Home Visit'),
|
||||
('CONSULTATION', 'Consultation'),
|
||||
('FOLLOW_UP', 'Follow-up'),
|
||||
('PROCEDURE', 'Procedure'),
|
||||
('SURGERY', 'Surgery'),
|
||||
('DIAGNOSTIC', 'Diagnostic'),
|
||||
('PREVENTIVE', 'Preventive Care'),
|
||||
('OTHER', 'Other'),
|
||||
],
|
||||
choices=EncounterType.choices,
|
||||
help_text='Type of encounter'
|
||||
)
|
||||
|
||||
encounter_class = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('AMB', 'Ambulatory'),
|
||||
('EMER', 'Emergency'),
|
||||
('FLD', 'Field'),
|
||||
('HH', 'Home Health'),
|
||||
('IMP', 'Inpatient'),
|
||||
('ACUTE', 'Inpatient Acute'),
|
||||
('NONAC', 'Inpatient Non-Acute'),
|
||||
('OBSENC', 'Observation Encounter'),
|
||||
('PRENC', 'Pre-Admission'),
|
||||
('SS', 'Short Stay'),
|
||||
('VR', 'Virtual'),
|
||||
],
|
||||
choices=EncounterClass.choices,
|
||||
help_text='Encounter class (HL7 standard)'
|
||||
)
|
||||
|
||||
@ -100,18 +119,8 @@ class Encounter(models.Model):
|
||||
# Status
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('PLANNED', 'Planned'),
|
||||
('ARRIVED', 'Arrived'),
|
||||
('TRIAGED', 'Triaged'),
|
||||
('IN_PROGRESS', 'In Progress'),
|
||||
('ON_HOLD', 'On Hold'),
|
||||
('FINISHED', 'Finished'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('ENTERED_IN_ERROR', 'Entered in Error'),
|
||||
('UNKNOWN', 'Unknown'),
|
||||
],
|
||||
default='PLANNED',
|
||||
choices=EncounterStatus.choices,
|
||||
default=EncounterStatus.PLANNED,
|
||||
help_text='Current encounter status'
|
||||
)
|
||||
|
||||
@ -158,18 +167,12 @@ class Encounter(models.Model):
|
||||
null=True,
|
||||
help_text='Reason for visit'
|
||||
)
|
||||
|
||||
|
||||
# Priority and Acuity
|
||||
priority = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('ROUTINE', 'Routine'),
|
||||
('URGENT', 'Urgent'),
|
||||
('ASAP', 'ASAP'),
|
||||
('STAT', 'STAT'),
|
||||
('EMERGENCY', 'Emergency'),
|
||||
],
|
||||
default='ROUTINE',
|
||||
choices=Priority.choices,
|
||||
default=Priority.ROUTINE,
|
||||
help_text='Encounter priority'
|
||||
)
|
||||
acuity_level = models.PositiveIntegerField(
|
||||
@ -271,6 +274,44 @@ class VitalSigns(models.Model):
|
||||
"""
|
||||
Vital signs model for tracking patient vital signs measurements.
|
||||
"""
|
||||
|
||||
class TemperatureMethod(models.TextChoices):
|
||||
ORAL = 'ORAL', 'Oral'
|
||||
RECTAL = 'RECTAL', 'Rectal'
|
||||
AXILLARY = 'AXILLARY', 'Axillary'
|
||||
TYMPANIC = 'TYMPANIC', 'Tympanic'
|
||||
TEMPORAL = 'TEMPORAL', 'Temporal'
|
||||
CORE = 'CORE', 'Core'
|
||||
|
||||
class BpPosition(models.TextChoices):
|
||||
SITTING = 'SITTING', 'Sitting'
|
||||
STANDING = 'STANDING', 'Standing'
|
||||
LYING = 'LYING', 'Lying'
|
||||
SUPINE = 'SUPINE', 'Supine'
|
||||
|
||||
class BpCuffSize(models.TextChoices):
|
||||
SMALL = 'SMALL', 'Small'
|
||||
REGULAR = 'REGULAR', 'Regular'
|
||||
LARGE = 'LARGE', 'Large'
|
||||
EXTRA_LARGE = 'EXTRA_LARGE', 'Extra Large'
|
||||
PEDIATRIC = 'PEDIATRIC', 'Pediatric'
|
||||
|
||||
class HeartRhythm(models.TextChoices):
|
||||
REGULAR = 'REGULAR', 'Regular'
|
||||
REGULARLY_IRREGULAR = 'REGULARLY_IRREGULAR', 'Regularly irregular'
|
||||
IRREGULARLY_IRREGULAR = 'IRREGULARLY_IRREGULAR', 'Irregularly irregular'
|
||||
IRREGULAR_UNSPECIFIED = 'IRREGULAR_UNSPECIFIED', 'Irregular (unspecified)'
|
||||
|
||||
class OxygenDelivery(models.TextChoices):
|
||||
ROOM_AIR = 'ROOM_AIR', 'Room Air'
|
||||
NASAL_CANNULA = 'NASAL_CANNULA', 'Nasal Cannula'
|
||||
SIMPLE_MASK = 'SIMPLE_MASK', 'Simple Mask'
|
||||
NON_REBREATHER = 'NON_REBREATHER', 'Non-Rebreather Mask'
|
||||
VENTURI_MASK = 'VENTURI_MASK', 'Venturi Mask'
|
||||
CPAP = 'CPAP', 'CPAP'
|
||||
BIPAP = 'BIPAP', 'BiPAP'
|
||||
MECHANICAL_VENTILATION = 'MECHANICAL_VENTILATION', 'Mechanical Ventilation'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
# Encounter relationship
|
||||
encounter = models.ForeignKey(
|
||||
@ -312,14 +353,7 @@ class VitalSigns(models.Model):
|
||||
)
|
||||
temperature_method = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('ORAL', 'Oral'),
|
||||
('RECTAL', 'Rectal'),
|
||||
('AXILLARY', 'Axillary'),
|
||||
('TYMPANIC', 'Tympanic'),
|
||||
('TEMPORAL', 'Temporal'),
|
||||
('CORE', 'Core'),
|
||||
],
|
||||
choices=TemperatureMethod.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Temperature measurement method'
|
||||
@ -340,25 +374,14 @@ class VitalSigns(models.Model):
|
||||
)
|
||||
bp_position = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('SITTING', 'Sitting'),
|
||||
('STANDING', 'Standing'),
|
||||
('LYING', 'Lying'),
|
||||
('SUPINE', 'Supine'),
|
||||
],
|
||||
choices=BpPosition.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Patient position during BP measurement'
|
||||
)
|
||||
bp_cuff_size = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('SMALL', 'Small'),
|
||||
('REGULAR', 'Regular'),
|
||||
('LARGE', 'Large'),
|
||||
('EXTRA_LARGE', 'Extra Large'),
|
||||
('PEDIATRIC', 'Pediatric'),
|
||||
],
|
||||
choices=BpCuffSize.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Blood pressure cuff size'
|
||||
@ -373,12 +396,7 @@ class VitalSigns(models.Model):
|
||||
)
|
||||
heart_rhythm = models.CharField(
|
||||
max_length=25,
|
||||
choices=[
|
||||
('REGULAR', 'Regular'),
|
||||
('IRREGULAR', 'Irregular'),
|
||||
('REGULARLY_IRREGULAR', 'Regularly Irregular'),
|
||||
('IRREGULARLY_IRREGULAR', 'Irregularly Irregular'),
|
||||
],
|
||||
choices=HeartRhythm.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Heart rhythm'
|
||||
@ -401,18 +419,8 @@ class VitalSigns(models.Model):
|
||||
)
|
||||
oxygen_delivery = models.CharField(
|
||||
max_length=30,
|
||||
choices=[
|
||||
('ROOM_AIR', 'Room Air'),
|
||||
('NASAL_CANNULA', 'Nasal Cannula'),
|
||||
('SIMPLE_MASK', 'Simple Mask'),
|
||||
('NON_REBREATHER', 'Non-Rebreather Mask'),
|
||||
('VENTURI_MASK', 'Venturi Mask'),
|
||||
('CPAP', 'CPAP'),
|
||||
('BIPAP', 'BiPAP'),
|
||||
('MECHANICAL_VENTILATION', 'Mechanical Ventilation'),
|
||||
('OTHER', 'Other'),
|
||||
],
|
||||
default='ROOM_AIR',
|
||||
choices=OxygenDelivery.choices,
|
||||
default=OxygenDelivery.ROOM_AIR,
|
||||
help_text='Oxygen delivery method'
|
||||
)
|
||||
oxygen_flow_rate = models.DecimalField(
|
||||
@ -572,7 +580,59 @@ class ProblemList(models.Model):
|
||||
"""
|
||||
Problem list model for tracking patient problems and diagnoses.
|
||||
"""
|
||||
|
||||
|
||||
class CodingSystem(models.TextChoices):
|
||||
ICD10 = 'ICD10', 'ICD-10'
|
||||
ICD9 = 'ICD9', 'ICD-9'
|
||||
SNOMED = 'SNOMED', 'SNOMED CT'
|
||||
CPT = 'CPT', 'CPT'
|
||||
LOINC = 'LOINC', 'LOINC'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class ProblemType(models.TextChoices):
|
||||
DIAGNOSIS = 'DIAGNOSIS', 'Diagnosis'
|
||||
SYMPTOM = 'SYMPTOM', 'Symptom'
|
||||
FINDING = 'FINDING', 'Finding'
|
||||
COMPLAINT = 'COMPLAINT', 'Complaint'
|
||||
CONDITION = 'CONDITION', 'Condition'
|
||||
DISORDER = 'DISORDER', 'Disorder'
|
||||
SYNDROME = 'SYNDROME', 'Syndrome'
|
||||
INJURY = 'INJURY', 'Injury'
|
||||
ALLERGY = 'ALLERGY', 'Allergy'
|
||||
INTOLERANCE = 'INTOLERANCE', 'Intolerance'
|
||||
RISK_FACTOR = 'RISK_FACTOR', 'Risk Factor'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class Severity(models.TextChoices):
|
||||
MILD = 'MILD', 'Mild'
|
||||
MODERATE = 'MODERATE', 'Moderate'
|
||||
SEVERE = 'SEVERE', 'Severe'
|
||||
CRITICAL = 'CRITICAL', 'Critical'
|
||||
UNKNOWN = 'UNKNOWN', 'Unknown'
|
||||
|
||||
class Priority(models.TextChoices):
|
||||
LOW = 'LOW', 'Low'
|
||||
MEDIUM = 'MEDIUM', 'Medium'
|
||||
HIGH = 'HIGH', 'High'
|
||||
URGENT = 'URGENT', 'Urgent'
|
||||
|
||||
class ProblemStatus(models.TextChoices):
|
||||
ACTIVE = 'ACTIVE', 'Active'
|
||||
INACTIVE = 'INACTIVE', 'Inactive'
|
||||
RESOLVED = 'RESOLVED', 'Resolved'
|
||||
REMISSION = 'REMISSION', 'In Remission'
|
||||
RECURRENCE = 'RECURRENCE', 'Recurrence'
|
||||
RELAPSE = 'RELAPSE', 'Relapse'
|
||||
UNKNOWN = 'UNKNOWN', 'Unknown'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class Laterality(models.TextChoices):
|
||||
LEFT = 'LEFT', 'Left'
|
||||
RIGHT = 'RIGHT', 'Right'
|
||||
BILATERAL = 'BILATERAL', 'Bilateral'
|
||||
UNILATERAL = 'UNILATERAL', 'Unilateral'
|
||||
NOT_APPLICABLE = 'NOT_APPLICABLE', 'Not Applicable'
|
||||
|
||||
# Tenant relationship
|
||||
tenant = models.ForeignKey(
|
||||
'core.Tenant',
|
||||
@ -610,14 +670,7 @@ class ProblemList(models.Model):
|
||||
)
|
||||
coding_system = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('ICD10', 'ICD-10'),
|
||||
('ICD9', 'ICD-9'),
|
||||
('SNOMED', 'SNOMED CT'),
|
||||
('CPT', 'CPT'),
|
||||
('LOINC', 'LOINC'),
|
||||
('OTHER', 'Other'),
|
||||
],
|
||||
choices=CodingSystem.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Coding system used'
|
||||
@ -626,20 +679,7 @@ class ProblemList(models.Model):
|
||||
# Problem Classification
|
||||
problem_type = models.CharField(
|
||||
max_length=30,
|
||||
choices=[
|
||||
('DIAGNOSIS', 'Diagnosis'),
|
||||
('SYMPTOM', 'Symptom'),
|
||||
('FINDING', 'Finding'),
|
||||
('COMPLAINT', 'Complaint'),
|
||||
('CONDITION', 'Condition'),
|
||||
('DISORDER', 'Disorder'),
|
||||
('SYNDROME', 'Syndrome'),
|
||||
('INJURY', 'Injury'),
|
||||
('ALLERGY', 'Allergy'),
|
||||
('INTOLERANCE', 'Intolerance'),
|
||||
('RISK_FACTOR', 'Risk Factor'),
|
||||
('OTHER', 'Other'),
|
||||
],
|
||||
choices=ProblemType.choices,
|
||||
help_text='Type of problem'
|
||||
)
|
||||
|
||||
@ -659,42 +699,23 @@ class ProblemList(models.Model):
|
||||
# Severity and Priority
|
||||
severity = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('MILD', 'Mild'),
|
||||
('MODERATE', 'Moderate'),
|
||||
('SEVERE', 'Severe'),
|
||||
('CRITICAL', 'Critical'),
|
||||
('UNKNOWN', 'Unknown'),
|
||||
],
|
||||
choices=Severity.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Problem severity'
|
||||
)
|
||||
priority = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('LOW', 'Low'),
|
||||
('MEDIUM', 'Medium'),
|
||||
('HIGH', 'High'),
|
||||
('URGENT', 'Urgent'),
|
||||
],
|
||||
default='MEDIUM',
|
||||
choices=Priority.choices,
|
||||
default=Priority.MEDIUM,
|
||||
help_text='Problem priority'
|
||||
)
|
||||
|
||||
# Status
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('ACTIVE', 'Active'),
|
||||
('INACTIVE', 'Inactive'),
|
||||
('RESOLVED', 'Resolved'),
|
||||
('REMISSION', 'In Remission'),
|
||||
('RECURRENCE', 'Recurrence'),
|
||||
('RELAPSE', 'Relapse'),
|
||||
('UNKNOWN', 'Unknown'),
|
||||
],
|
||||
default='ACTIVE',
|
||||
choices=ProblemStatus.choices,
|
||||
default=ProblemStatus.ACTIVE,
|
||||
help_text='Current problem status'
|
||||
)
|
||||
|
||||
@ -745,13 +766,7 @@ class ProblemList(models.Model):
|
||||
)
|
||||
laterality = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('LEFT', 'Left'),
|
||||
('RIGHT', 'Right'),
|
||||
('BILATERAL', 'Bilateral'),
|
||||
('UNILATERAL', 'Unilateral'),
|
||||
('NOT_APPLICABLE', 'Not Applicable'),
|
||||
],
|
||||
choices=Laterality.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Laterality'
|
||||
@ -850,7 +865,49 @@ class CarePlan(models.Model):
|
||||
"""
|
||||
Care plan model for managing patient care coordination and goals.
|
||||
"""
|
||||
|
||||
class PlanType(models.TextChoices):
|
||||
COMPREHENSIVE = 'COMPREHENSIVE', 'Comprehensive Care Plan'
|
||||
DISEASE_SPECIFIC = 'DISEASE_SPECIFIC', 'Disease-Specific Plan'
|
||||
PREVENTIVE = 'PREVENTIVE', 'Preventive Care Plan'
|
||||
CHRONIC_CARE = 'CHRONIC_CARE', 'Chronic Care Management'
|
||||
ACUTE_CARE = 'ACUTE_CARE', 'Acute Care Plan'
|
||||
DISCHARGE = 'DISCHARGE', 'Discharge Planning'
|
||||
REHABILITATION = 'REHABILITATION', 'Rehabilitation Plan'
|
||||
PALLIATIVE = 'PALLIATIVE', 'Palliative Care Plan'
|
||||
MENTAL_HEALTH = 'MENTAL_HEALTH', 'Mental Health Plan'
|
||||
MEDICATION = 'MEDICATION', 'Medication Management'
|
||||
NUTRITION = 'NUTRITION', 'Nutrition Plan'
|
||||
EXERCISE = 'EXERCISE', 'Exercise Plan'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class PlanCategory(models.TextChoices):
|
||||
ASSESSMENT = 'ASSESSMENT', 'Assessment and Monitoring'
|
||||
TREATMENT = 'TREATMENT', 'Treatment'
|
||||
EDUCATION = 'EDUCATION', 'Patient Education'
|
||||
COORDINATION = 'COORDINATION', 'Care Coordination'
|
||||
PREVENTION = 'PREVENTION', 'Prevention'
|
||||
LIFESTYLE = 'LIFESTYLE', 'Lifestyle Modification'
|
||||
MEDICATION = 'MEDICATION', 'Medication Management'
|
||||
FOLLOW_UP = 'FOLLOW_UP', 'Follow-up Care'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency Planning'
|
||||
SUPPORT = 'SUPPORT', 'Support Services'
|
||||
|
||||
class PlanStatus(models.TextChoices):
|
||||
DRAFT = 'DRAFT', 'Draft'
|
||||
ACTIVE = 'ACTIVE', 'Active'
|
||||
ON_HOLD = 'ON_HOLD', 'On Hold'
|
||||
COMPLETED = 'COMPLETED', 'Completed'
|
||||
CANCELLED = 'CANCELLED', 'Cancelled'
|
||||
ENTERED_IN_ERROR = 'ENTERED_IN_ERROR', 'Entered in Error'
|
||||
UNKNOWN = 'UNKNOWN', 'Unknown'
|
||||
|
||||
class PlanPriority(models.TextChoices):
|
||||
LOW = 'LOW', 'Low'
|
||||
ROUTINE = 'ROUTINE', 'Routine'
|
||||
URGENT = 'URGENT', 'Urgent'
|
||||
STAT = 'STAT', 'STAT'
|
||||
|
||||
|
||||
# Tenant relationship
|
||||
tenant = models.ForeignKey(
|
||||
'core.Tenant',
|
||||
@ -887,38 +944,15 @@ class CarePlan(models.Model):
|
||||
# Plan Type and Category
|
||||
plan_type = models.CharField(
|
||||
max_length=30,
|
||||
choices=[
|
||||
('COMPREHENSIVE', 'Comprehensive Care Plan'),
|
||||
('DISEASE_SPECIFIC', 'Disease-Specific Plan'),
|
||||
('PREVENTIVE', 'Preventive Care Plan'),
|
||||
('CHRONIC_CARE', 'Chronic Care Management'),
|
||||
('ACUTE_CARE', 'Acute Care Plan'),
|
||||
('DISCHARGE', 'Discharge Planning'),
|
||||
('REHABILITATION', 'Rehabilitation Plan'),
|
||||
('PALLIATIVE', 'Palliative Care Plan'),
|
||||
('MENTAL_HEALTH', 'Mental Health Plan'),
|
||||
('MEDICATION', 'Medication Management'),
|
||||
('NUTRITION', 'Nutrition Plan'),
|
||||
('EXERCISE', 'Exercise Plan'),
|
||||
('OTHER', 'Other'),
|
||||
],
|
||||
choices=PlanType.choices,
|
||||
help_text='Type of care plan'
|
||||
)
|
||||
|
||||
|
||||
|
||||
category = models.CharField(
|
||||
max_length=50,
|
||||
choices=[
|
||||
('ASSESSMENT', 'Assessment and Monitoring'),
|
||||
('TREATMENT', 'Treatment'),
|
||||
('EDUCATION', 'Patient Education'),
|
||||
('COORDINATION', 'Care Coordination'),
|
||||
('PREVENTION', 'Prevention'),
|
||||
('LIFESTYLE', 'Lifestyle Modification'),
|
||||
('MEDICATION', 'Medication Management'),
|
||||
('FOLLOW_UP', 'Follow-up Care'),
|
||||
('EMERGENCY', 'Emergency Planning'),
|
||||
('SUPPORT', 'Support Services'),
|
||||
],
|
||||
choices=PlanCategory.choices,
|
||||
help_text='Care plan category'
|
||||
)
|
||||
|
||||
@ -940,30 +974,16 @@ class CarePlan(models.Model):
|
||||
# Status
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('DRAFT', 'Draft'),
|
||||
('ACTIVE', 'Active'),
|
||||
('ON_HOLD', 'On Hold'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('CANCELLED', 'Cancelled'),
|
||||
('ENTERED_IN_ERROR', 'Entered in Error'),
|
||||
('UNKNOWN', 'Unknown'),
|
||||
],
|
||||
default='DRAFT',
|
||||
choices=PlanStatus.choices,
|
||||
default=PlanStatus.DRAFT,
|
||||
help_text='Care plan status'
|
||||
)
|
||||
|
||||
# Priority
|
||||
priority = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('LOW', 'Low'),
|
||||
('ROUTINE', 'Routine'),
|
||||
('URGENT', 'Urgent'),
|
||||
('ASAP', 'ASAP'),
|
||||
('STAT', 'STAT'),
|
||||
],
|
||||
default='ROUTINE',
|
||||
choices=PlanPriority.choices,
|
||||
default=PlanPriority.ROUTINE,
|
||||
help_text='Care plan priority'
|
||||
)
|
||||
|
||||
@ -1142,7 +1162,47 @@ class ClinicalNote(models.Model):
|
||||
"""
|
||||
Clinical note model for documenting patient care and observations.
|
||||
"""
|
||||
|
||||
|
||||
class NoteType(models.TextChoices):
|
||||
PROGRESS = 'PROGRESS', 'Progress'
|
||||
ADMISSION = 'ADMISSION', 'Admission Note'
|
||||
DISCHARGE = 'DISCHARGE', 'Discharge Note'
|
||||
CONSULTATION = 'CONSULTATION', 'Consultation Note'
|
||||
PROCEDURE = 'PROCEDURE', 'Procedure Note'
|
||||
OPERATIVE = 'OPERATIVE', 'Operative Note'
|
||||
NURSING = 'NURSING', 'Nursing Note'
|
||||
THERAPY = 'THERAPY', 'Therapy Note'
|
||||
SOCIAL_WORK = 'SOCIAL_WORK', 'Social Work Note'
|
||||
PSYCHOLOGY = 'PSYCHOLOGY', 'Psychology Note'
|
||||
NUTRITION = 'NUTRITION', 'Nutrition Note'
|
||||
PHARMACY = 'PHARMACY', 'Pharmacy Note'
|
||||
CASE_MANAGEMENT = 'CASE_MANAGEMENT', 'Case Management Note'
|
||||
EDUCATION = 'EDUCATION', 'Patient Education Note'
|
||||
TELEPHONE = 'TELEPHONE', 'Telephone Note'
|
||||
ADDENDUM = 'ADDENDUM', 'Addendum'
|
||||
CORRECTION = 'CORRECTION', 'Correction'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class NoteStatus(models.TextChoices):
|
||||
DRAFT = 'DRAFT', 'Draft'
|
||||
IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
|
||||
COMPLETED = 'COMPLETED', 'Completed'
|
||||
SIGNED = 'SIGNED', 'Signed'
|
||||
AMENDED = 'AMENDED', 'Amended'
|
||||
CORRECTED = 'CORRECTED', 'Corrected'
|
||||
CANCELLED = 'CANCELLED', 'Cancelled'
|
||||
ERROR = 'ERROR', 'Entered in Error'
|
||||
UNKNOWN = 'UNKNOWN', 'Unknown'
|
||||
|
||||
class NoteSignatureMethod(models.TextChoices):
|
||||
ELECTRONIC = 'ELECTRONIC', 'Electronic'
|
||||
DIGITAL = 'DIGITAL', 'Digital Signature'
|
||||
BIOMETRIC = 'BIOMETRIC', 'Biometric Signature'
|
||||
PASSWORD = 'PASSWORD', 'Password'
|
||||
TOKEN = 'TOKEN', 'Token Authentication'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
|
||||
# Encounter relationship
|
||||
encounter = models.ForeignKey(
|
||||
Encounter,
|
||||
@ -1166,30 +1226,11 @@ class ClinicalNote(models.Model):
|
||||
editable=False,
|
||||
help_text='Unique note identifier'
|
||||
)
|
||||
|
||||
|
||||
# Note Details
|
||||
note_type = models.CharField(
|
||||
max_length=30,
|
||||
choices=[
|
||||
('PROGRESS', 'Progress Note'),
|
||||
('ADMISSION', 'Admission Note'),
|
||||
('DISCHARGE', 'Discharge Note'),
|
||||
('CONSULTATION', 'Consultation Note'),
|
||||
('PROCEDURE', 'Procedure Note'),
|
||||
('OPERATIVE', 'Operative Note'),
|
||||
('NURSING', 'Nursing Note'),
|
||||
('THERAPY', 'Therapy Note'),
|
||||
('SOCIAL_WORK', 'Social Work Note'),
|
||||
('PSYCHOLOGY', 'Psychology Note'),
|
||||
('NUTRITION', 'Nutrition Note'),
|
||||
('PHARMACY', 'Pharmacy Note'),
|
||||
('CASE_MANAGEMENT', 'Case Management Note'),
|
||||
('EDUCATION', 'Patient Education Note'),
|
||||
('TELEPHONE', 'Telephone Note'),
|
||||
('ADDENDUM', 'Addendum'),
|
||||
('CORRECTION', 'Correction'),
|
||||
('OTHER', 'Other'),
|
||||
],
|
||||
choices=NoteType.choices,
|
||||
help_text='Type of clinical note'
|
||||
)
|
||||
|
||||
@ -1235,16 +1276,8 @@ class ClinicalNote(models.Model):
|
||||
# Status and Workflow
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('DRAFT', 'Draft'),
|
||||
('IN_PROGRESS', 'In Progress'),
|
||||
('COMPLETED', 'Completed'),
|
||||
('SIGNED', 'Signed'),
|
||||
('AMENDED', 'Amended'),
|
||||
('CORRECTED', 'Corrected'),
|
||||
('ENTERED_IN_ERROR', 'Entered in Error'),
|
||||
],
|
||||
default='DRAFT',
|
||||
choices=NoteStatus.choices,
|
||||
default=NoteStatus.DRAFT,
|
||||
help_text='Note status'
|
||||
)
|
||||
|
||||
@ -1260,13 +1293,7 @@ class ClinicalNote(models.Model):
|
||||
)
|
||||
signature_method = models.CharField(
|
||||
max_length=20,
|
||||
choices=[
|
||||
('ELECTRONIC', 'Electronic Signature'),
|
||||
('DIGITAL', 'Digital Signature'),
|
||||
('BIOMETRIC', 'Biometric Signature'),
|
||||
('PASSWORD', 'Password Authentication'),
|
||||
('TOKEN', 'Token Authentication'),
|
||||
],
|
||||
choices=NoteSignatureMethod.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Method of signature'
|
||||
@ -1374,7 +1401,46 @@ class NoteTemplate(models.Model):
|
||||
"""
|
||||
Note template model for standardizing clinical documentation.
|
||||
"""
|
||||
|
||||
|
||||
class NoteType(models.TextChoices):
|
||||
PROGRESS = 'PROGRESS', 'Progress Note'
|
||||
ADMISSION = 'ADMISSION', 'Admission Note'
|
||||
DISCHARGE = 'DISCHARGE', 'Discharge Note'
|
||||
CONSULTATION = 'CONSULTATION', 'Consultation Note'
|
||||
PROCEDURE = 'PROCEDURE', 'Procedure Note'
|
||||
OPERATIVE = 'OPERATIVE', 'Operative Note'
|
||||
NURSING = 'NURSING', 'Nursing Note'
|
||||
THERAPY = 'THERAPY', 'Therapy Note'
|
||||
SOCIAL_WORK = 'SOCIAL_WORK', 'Social Work Note'
|
||||
PSYCHOLOGY = 'PSYCHOLOGY', 'Psychology Note'
|
||||
NUTRITION = 'NUTRITION', 'Nutrition Note'
|
||||
PHARMACY = 'PHARMACY', 'Pharmacy Note'
|
||||
CASE_MANAGEMENT = 'CASE_MANAGEMENT', 'Case Management Note'
|
||||
EDUCATION = 'EDUCATION', 'Patient Education Note'
|
||||
TELEPHONE = 'TELEPHONE', 'Telephone Note'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
class NoteSpecialty(models.TextChoices):
|
||||
GENERAL_MEDICINE = 'GENERAL_MEDICINE', 'General Medicine'
|
||||
SURGERY = 'SURGERY', 'Surgery'
|
||||
CARDIOLOGY = 'CARDIOLOGY', 'Cardiology'
|
||||
NEUROLOGY = 'NEUROLOGY', 'Neurology'
|
||||
ONCOLOGY = 'ONCOLOGY', 'Oncology'
|
||||
PEDIATRICS = 'PEDIATRICS', 'Pediatrics'
|
||||
OBSTETRICS = 'OBSTETRICS', 'Obstetrics'
|
||||
GYNECOLOGY = 'GYNECOLOGY', 'Gynecology'
|
||||
ORTHOPEDICS = 'ORTHOPEDICS', 'Orthopedics'
|
||||
PSYCHIATRY = 'PSYCHIATRY', 'Psychiatry'
|
||||
EMERGENCY = 'EMERGENCY', 'Emergency Medicine'
|
||||
CRITICAL_CARE = 'CRITICAL_CARE', 'Critical Care'
|
||||
REHABILITATION = 'REHABILITATION', 'Rehabilitation'
|
||||
NURSING = 'NURSING', 'Nursing'
|
||||
THERAPY = 'THERAPY', 'Therapy'
|
||||
SOCIAL_WORK = 'SOCIAL_WORK', 'Social Work'
|
||||
NUTRITION = 'NUTRITION', 'Nutrition'
|
||||
PHARMACY = 'PHARMACY', 'Pharmacy'
|
||||
OTHER = 'OTHER', 'Other'
|
||||
|
||||
# Tenant relationship
|
||||
tenant = models.ForeignKey(
|
||||
'core.Tenant',
|
||||
@ -1404,50 +1470,13 @@ class NoteTemplate(models.Model):
|
||||
# Template Type
|
||||
note_type = models.CharField(
|
||||
max_length=30,
|
||||
choices=[
|
||||
('PROGRESS', 'Progress Note'),
|
||||
('ADMISSION', 'Admission Note'),
|
||||
('DISCHARGE', 'Discharge Note'),
|
||||
('CONSULTATION', 'Consultation Note'),
|
||||
('PROCEDURE', 'Procedure Note'),
|
||||
('OPERATIVE', 'Operative Note'),
|
||||
('NURSING', 'Nursing Note'),
|
||||
('THERAPY', 'Therapy Note'),
|
||||
('SOCIAL_WORK', 'Social Work Note'),
|
||||
('PSYCHOLOGY', 'Psychology Note'),
|
||||
('NUTRITION', 'Nutrition Note'),
|
||||
('PHARMACY', 'Pharmacy Note'),
|
||||
('CASE_MANAGEMENT', 'Case Management Note'),
|
||||
('EDUCATION', 'Patient Education Note'),
|
||||
('TELEPHONE', 'Telephone Note'),
|
||||
('OTHER', 'Other'),
|
||||
],
|
||||
choices=NoteType.choices,
|
||||
help_text='Type of note this template is for'
|
||||
)
|
||||
|
||||
specialty = models.CharField(
|
||||
max_length=100,
|
||||
choices=[
|
||||
('GENERAL_MEDICINE', 'General Medicine'),
|
||||
('SURGERY', 'Surgery'),
|
||||
('CARDIOLOGY', 'Cardiology'),
|
||||
('NEUROLOGY', 'Neurology'),
|
||||
('ONCOLOGY', 'Oncology'),
|
||||
('PEDIATRICS', 'Pediatrics'),
|
||||
('OBSTETRICS', 'Obstetrics'),
|
||||
('GYNECOLOGY', 'Gynecology'),
|
||||
('ORTHOPEDICS', 'Orthopedics'),
|
||||
('PSYCHIATRY', 'Psychiatry'),
|
||||
('EMERGENCY', 'Emergency Medicine'),
|
||||
('CRITICAL_CARE', 'Critical Care'),
|
||||
('REHABILITATION', 'Rehabilitation'),
|
||||
('NURSING', 'Nursing'),
|
||||
('THERAPY', 'Therapy'),
|
||||
('SOCIAL_WORK', 'Social Work'),
|
||||
('NUTRITION', 'Nutrition'),
|
||||
('PHARMACY', 'Pharmacy'),
|
||||
('OTHER', 'Other'),
|
||||
],
|
||||
choices=NoteSpecialty.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='Medical specialty'
|
||||
@ -1543,3 +1572,32 @@ class NoteTemplate(models.Model):
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Icd10(models.Model):
|
||||
"""
|
||||
ICD-10-CM tabular code entry.
|
||||
Handles chapters/sections/diagnoses (with parent-child hierarchy).
|
||||
"""
|
||||
code = models.CharField(max_length=10, unique=True, db_index=True)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
chapter_name = models.CharField(max_length=255, blank=True, null=True)
|
||||
section_name = models.CharField(max_length=255, blank=True, null=True)
|
||||
parent = models.ForeignKey(
|
||||
'self',
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name='children'
|
||||
)
|
||||
is_header = models.BooleanField(default=False)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'emr_icd10'
|
||||
verbose_name = 'ICD-10 Code'
|
||||
verbose_name_plural = 'ICD-10 Codes'
|
||||
ordering = ['code']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.code} — {self.description[:80] if self.description else ''}"
|
||||
@ -148,8 +148,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<span class="badge care-plan-status-{{ care_plan.status|lower }} me-2">{{ care_plan.get_status_display }}</span>
|
||||
<span class="badge care-plan-priority-{{ care_plan.priority|lower }}">{{ care_plan.get_priority_display }} Priority</span>
|
||||
<p class="fw-bold me-2">Status:
|
||||
<span class="badge care-plan-status-{{ care_plan.status|lower }}">{{ care_plan.get_status_display }}</span>
|
||||
</p>
|
||||
<p class="fw-bold me-2">Priority:
|
||||
<span class="badge care-plan-priority-{{ care_plan.priority|lower }}">{{ care_plan.get_priority_display }}</span>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- end care plan header -->
|
||||
@ -161,18 +167,18 @@
|
||||
|
||||
<div class="panel-heading-btn">
|
||||
<a href="{% url 'patients:patient_detail' care_plan.patient.id %}" class="btn btn-xs btn-outline-secondary me-2">
|
||||
<i class="fa fa-user me-1"></i> Patient Profile
|
||||
<i class="fa fa-user me-1"></i>Patient Profile
|
||||
</a>
|
||||
{% if not care_plan.approved %}
|
||||
<a href="#" class="btn btn-xs btn-outline-success me-2" data-bs-toggle="modal" data-bs-target="#approveModal">
|
||||
<i class="fa fa-check-circle me-1"></i> Approve Plan
|
||||
<i class="fa fa-check-circle me-1"></i>Approve Plan
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'emr:care_plan_update' care_plan.id %}" class="btn btn-xs btn-outline-warning me-2">
|
||||
<i class="fa fa-edit me-1"></i> Edit
|
||||
<i class="fa fa-edit me-1"></i>Edit
|
||||
</a>
|
||||
<a href="{% url 'emr:care_plan_delete' care_plan.id %}" class="btn btn-xs btn-outline-danger me-2">
|
||||
<i class="fa fa-trash me-1"></i> Delete
|
||||
<i class="fa fa-trash me-1"></i>Delete
|
||||
</a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
@ -448,7 +454,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<div class="note alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No objectives have been defined for this care plan.
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -475,7 +481,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<div class="note alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No interventions have been defined for this care plan.
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -497,7 +503,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<div class="note alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No activities have been defined for this care plan.
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -549,7 +555,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<div class="note alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No evaluation criteria have been defined for this care plan.
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -603,7 +609,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info mb-4">
|
||||
<div class="note alert-info mb-4">
|
||||
<i class="fa fa-info-circle me-2"></i> No resources have been identified for this care plan.
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -627,7 +633,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<div class="note alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No support systems have been identified for this care plan.
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -654,7 +660,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<div class="note alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No outcomes have been recorded for this care plan.
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -678,6 +684,7 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if care_plan.clinical_notes.all %}
|
||||
|
||||
<div class="timeline">
|
||||
{% for note in care_plan.clinical_notes.all %}
|
||||
<div class="timeline-item">
|
||||
@ -702,7 +709,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<div class="note alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No clinical notes are associated with this care plan.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load static custom_filters%}
|
||||
|
||||
{% block title %}
|
||||
{% if form.instance.id %}Edit Care Plan: {{ form.instance.title }}{% else %}New Care Plan{% endif %}
|
||||
@ -142,19 +142,15 @@
|
||||
<h5 class="form-section-title">Basic Information</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3 required-field">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.patient.id_for_label }}">Patient</label>
|
||||
{{ form.patient }}
|
||||
<label for="{{ form.patient.id_for_label }}">Patient</label>
|
||||
</div>
|
||||
{% if form.patient.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.patient.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6 mb-3 required-field">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.title.id_for_label }}">Care Plan Title</label>
|
||||
{{ form.title }}
|
||||
<label for="{{ form.title.id_for_label }}">Care Plan Title</label>
|
||||
</div>
|
||||
{% if form.title.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.title.errors }}</div>
|
||||
{% endif %}
|
||||
@ -163,19 +159,15 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3 required-field">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.plan_type.id_for_label }}">Plan Type</label>
|
||||
{{ form.plan_type }}
|
||||
<label for="{{ form.plan_type.id_for_label }}">Plan Type</label>
|
||||
</div>
|
||||
{% if form.plan_type.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.plan_type.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6 mb-3 required-field">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.category.id_for_label }}">Category</label>
|
||||
{{ form.category }}
|
||||
<label for="{{ form.category.id_for_label }}">Category</label>
|
||||
</div>
|
||||
{% if form.category.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.category.errors }}</div>
|
||||
{% endif %}
|
||||
@ -184,7 +176,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-3">
|
||||
<label for="{{ form.description.id_for_label }}">Description</label>
|
||||
<label class="form-label" for="{{ form.description.id_for_label }}">Description</label>
|
||||
{{ form.description }}
|
||||
{% if form.description.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.description.errors }}</div>
|
||||
@ -198,28 +190,22 @@
|
||||
<h5 class="form-section-title">Timeline</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3 required-field">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.start_date.id_for_label }}">Start Date</label>
|
||||
{{ form.start_date }}
|
||||
<label for="{{ form.start_date.id_for_label }}">Start Date</label>
|
||||
</div>
|
||||
{% if form.start_date.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.start_date.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.target_completion_date.id_for_label }}">Target Completion Date</label>
|
||||
{{ form.target_completion_date }}
|
||||
<label for="{{ form.target_completion_date.id_for_label }}">Target Completion Date</label>
|
||||
</div>
|
||||
{% if form.target_completion_date.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.target_completion_date.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.end_date.id_for_label }}">End Date</label>
|
||||
{{ form.end_date }}
|
||||
<label for="{{ form.end_date.id_for_label }}">End Date</label>
|
||||
</div>
|
||||
{% if form.end_date.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.end_date.errors }}</div>
|
||||
{% endif %}
|
||||
@ -228,28 +214,22 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.next_review_date.id_for_label }}">Next Review Date</label>
|
||||
{{ form.next_review_date }}
|
||||
<label for="{{ form.next_review_date.id_for_label }}">Next Review Date</label>
|
||||
</div>
|
||||
{% if form.next_review_date.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.next_review_date.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4 mb-3 required-field">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.status.id_for_label }}">Status</label>
|
||||
{{ form.status }}
|
||||
<label for="{{ form.status.id_for_label }}">Status</label>
|
||||
</div>
|
||||
{% if form.status.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.status.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4 mb-3 required-field">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.priority.id_for_label }}">Priority</label>
|
||||
{{ form.priority }}
|
||||
<label for="{{ form.priority.id_for_label }}">Priority</label>
|
||||
</div>
|
||||
{% if form.priority.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.priority.errors }}</div>
|
||||
{% endif %}
|
||||
@ -258,10 +238,8 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3 required-field">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.completion_percentage.id_for_label }}">Completion Percentage</label>
|
||||
{{ form.completion_percentage }}
|
||||
<label for="{{ form.completion_percentage.id_for_label }}">Completion Percentage</label>
|
||||
</div>
|
||||
{% if form.completion_percentage.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.completion_percentage.errors }}</div>
|
||||
{% endif %}
|
||||
@ -271,8 +249,8 @@
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="form-check form-switch mt-4">
|
||||
{{ form.approved }}
|
||||
<label class="form-check-label" for="{{ form.approved.id_for_label }}">Approved</label>
|
||||
{{ form.approved }}
|
||||
</div>
|
||||
{% if form.approved.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.approved.errors }}</div>
|
||||
@ -280,7 +258,7 @@
|
||||
<div class="approved-fields" style="display: none;">
|
||||
<div class="form-floating mt-2">
|
||||
{{ form.approved_by }}
|
||||
<label for="{{ form.approved_by.id_for_label }}">Approved By</label>
|
||||
<label class="form-label" for="{{ form.approved_by.id_for_label }}">Approved By</label>
|
||||
</div>
|
||||
{% if form.approved_by.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.approved_by.errors }}</div>
|
||||
@ -295,17 +273,15 @@
|
||||
<h5 class="form-section-title">Provider Information</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3 required-field">
|
||||
<div class="form-floating">
|
||||
<label class="form-label" for="{{ form.primary_provider.id_for_label }}">Primary Provider</label>
|
||||
{{ form.primary_provider }}
|
||||
<label for="{{ form.primary_provider.id_for_label }}">Primary Provider</label>
|
||||
</div>
|
||||
{% if form.primary_provider.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.primary_provider.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.care_team.id_for_label }}">Care Team</label>
|
||||
{{ form.care_team }}
|
||||
<label class="form-label" for="{{ form.care_team.id_for_label }}">Care Team</label>
|
||||
{{ form.care_team|attr:'multiple' }}
|
||||
{% if form.care_team.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.care_team.errors }}</div>
|
||||
{% endif %}
|
||||
@ -318,8 +294,8 @@
|
||||
<h5 class="form-section-title">Related Problems</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-3">
|
||||
<label for="{{ form.related_problems.id_for_label }}">Related Problems</label>
|
||||
{{ form.related_problems }}
|
||||
<label class="form-label" for="{{ form.related_problems.id_for_label }}">Related Problems</label>
|
||||
{{ form.related_problems|attr:'multiple' }}
|
||||
{% if form.related_problems.errors %}
|
||||
<div class="invalid-feedback d-block">{{ form.related_problems.errors }}</div>
|
||||
{% endif %}
|
||||
@ -362,15 +338,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="form-floating">
|
||||
<select class="form-select" name="goal_status[]">
|
||||
<label>Status</label>
|
||||
<select class="form-select form-select-sm" name="goal_status[]">
|
||||
<option value="not_started">Not Started</option>
|
||||
<option value="in_progress">In Progress</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="cancelled">Cancelled</option>
|
||||
</select>
|
||||
<label>Status</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -413,15 +388,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="form-floating">
|
||||
<label>Status</label>
|
||||
<select class="form-select" name="objective_status[]">
|
||||
<option value="not_started">Not Started</option>
|
||||
<option value="in_progress">In Progress</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="cancelled">Cancelled</option>
|
||||
</select>
|
||||
<label>Status</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -468,15 +441,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="form-floating">
|
||||
<label>Status</label>
|
||||
<select class="form-select" name="intervention_status[]">
|
||||
<option value="not_started">Not Started</option>
|
||||
<option value="in_progress">In Progress</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="cancelled">Cancelled</option>
|
||||
</select>
|
||||
<label>Status</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -63,9 +63,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Care Plans Table -->
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<table class="table table-sm table-hover mb-0 align-middle">
|
||||
<thead class="table-primary">
|
||||
<tr>
|
||||
<th>Patient</th>
|
||||
<th>Care Plan</th>
|
||||
@ -145,16 +146,17 @@
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
{% if plan.status == 'ACTIVE' %}
|
||||
<button class="btn btn-outline-info" title="Update Progress">
|
||||
<a href="{% url 'emr:care_plan_progress_update' plan.id %}" class="btn btn-outline-info" title="Update Progress">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</a>
|
||||
<button class="btn btn-outline-success"
|
||||
title="Mark Complete"
|
||||
hx-post="{% url 'emr:complete_care_plan' plan.id %}"
|
||||
hx-confirm="Mark this care plan as complete?"
|
||||
hx-swap="none"
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
||||
<i class="fas fa-check"></i>
|
||||
</button>
|
||||
{# <button class="btn btn-outline-success" #}
|
||||
{# title="Mark Complete"#}
|
||||
{# hx-post="{% url 'emr:complete_care_plan' plan.id %}"#}
|
||||
{# hx-confirm="Mark this care plan as complete?"#}
|
||||
{# hx-swap="none">#}
|
||||
{# <i class="fas fa-check"></i>#}
|
||||
{# </button>#}
|
||||
{% endif %}
|
||||
<button class="btn btn-outline-secondary" title="Notes">
|
||||
<i class="fas fa-sticky-note"></i>
|
||||
@ -180,6 +182,7 @@
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
153
emr/templates/emr/care_plans/care_plan_progress_form.html
Normal file
153
emr/templates/emr/care_plans/care_plan_progress_form.html
Normal file
@ -0,0 +1,153 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block css %}
|
||||
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/bootstrap-icons/font/bootstrap-icons.css' %}" rel="stylesheet" />
|
||||
<style>
|
||||
.form-floating > .form-control:focus ~ label,
|
||||
.form-floating > .form-control:not(:placeholder-shown) ~ label,
|
||||
.form-floating > .form-select ~ label {
|
||||
opacity: .65;
|
||||
transform: scale(.85) translateY(-0.5rem) translateX(0.15rem);
|
||||
}
|
||||
.form-floating > label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
padding: 1rem 0.75rem;
|
||||
pointer-events: none;
|
||||
border: 1px solid transparent;
|
||||
transform-origin: 0 0;
|
||||
transition: opacity .1s ease-in-out, transform .1s ease-in-out;
|
||||
}
|
||||
.form-section {
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: rgba(var(--bs-light-rgb), 0.5);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.form-section-title {
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--bs-gray-300);
|
||||
}
|
||||
.required-field label::after {
|
||||
content: " *";
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
.help-sidebar {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
}
|
||||
.help-card {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.help-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.dynamic-formset {
|
||||
border: 1px solid var(--bs-gray-300);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
.dynamic-formset .remove-formset {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
.preview-section {
|
||||
border: 1px solid var(--bs-gray-300);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.preview-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.preview-content {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.preview-badge {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.35em 0.65em;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xl-12">
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<i class="fas fa-tasks me-2"></i>Update Care Plan Progress — {{ object.title }}
|
||||
</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">{{ form.status.label_tag }} {{ form.status }}</div>
|
||||
<div class="col-md-4">{{ form.completion_percentage.label_tag }} {{ form.completion_percentage }}</div>
|
||||
<div class="col-md-4">{{ form.last_reviewed.label_tag }} {{ form.last_reviewed }}</div>
|
||||
<div class="col-md-4">{{ form.next_review_date.label_tag }} {{ form.next_review_date }}</div>
|
||||
<div class="col-12">{{ form.progress_notes.label_tag }} {{ form.progress_notes }}</div>
|
||||
<div class="col-12">{{ form.monitoring_parameters.label_tag }} {{ form.monitoring_parameters }}</div>
|
||||
<div class="col-12">{{ form.evaluation_criteria.label_tag }} {{ form.evaluation_criteria }}</div>
|
||||
<div class="col-12">{{ form.outcomes_achieved.label_tag }} {{ form.outcomes_achieved }}</div>
|
||||
</div>
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger mt-3">{{ form.non_field_errors }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-4 d-flex gap-2">
|
||||
<button class="btn btn-primary btn-sm" type="submit">
|
||||
<i class="fas fa-save me-1"></i> Save Progress
|
||||
</button>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'emr:care_plan_detail' object.pk %}">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Initialize select2
|
||||
$('.form-select').select2({
|
||||
theme: 'bootstrap',
|
||||
width: '100%'
|
||||
});
|
||||
|
||||
// Initialize datepicker
|
||||
$('.datepicker').datepicker({
|
||||
format: 'yyyy-mm-dd',
|
||||
autoclose: true,
|
||||
todayHighlight: true
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
120
emr/templates/emr/icd10_detail.html
Normal file
120
emr/templates/emr/icd10_detail.html
Normal file
@ -0,0 +1,120 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<div>
|
||||
<h1 class="h2">
|
||||
<i class="fas fa-dna me-2"></i>
|
||||
ICD10
|
||||
<span class="fw-light">
|
||||
<span class="fw-light">{{ record.code }}</span>
|
||||
</span>
|
||||
{% if record.is_header %}
|
||||
<span class="badge bg-secondary">Header</span>
|
||||
{% endif %}
|
||||
</h1>
|
||||
<p class="text-muted">{{ record.description }}</p>
|
||||
</div>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<a href="{% url 'emr:icd10_search' %}" class="btn btn-sm btn-outline-theme">
|
||||
<i class="fas fa-arrow-alt-circle-left me-2"></i> Back to search
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="panel panel-inverse" data-sortable-id="index-1">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><i class="fas fa-search me-2"></i> {{ _("Search")}}</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% if ancestors %}
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb small">
|
||||
{% for a in ancestors %}
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'emr:icd10_detail' a.code %}">{{ a.code }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ record.code }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
{% endif %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
{% if record %}
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-3">Code</dt>
|
||||
<dd class="col-sm-9">{{ record.code }}</dd>
|
||||
<dt class="col-sm-3">Description</dt>
|
||||
<dd class="col-sm-9">
|
||||
{{ record.description|default:"—" }}
|
||||
</dd>
|
||||
{% if record.section_name or record.chapter_name %}
|
||||
<dt class="col-sm-3">Context</dt>
|
||||
<dd class="col-sm-9">
|
||||
{% if record.section_name %}<span class="me-2">Section: {{ record.section_name }}</span>{% endif %}
|
||||
{% if record.chapter_name %}<span>Chapter: {{ record.chapter_name }}</span>{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
{% else %}
|
||||
<p class="text-muted mb-0">No data for this code.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h2 class="h6 mb-0">Subcodes</h2>
|
||||
{% if children %}
|
||||
<span class="text-muted small">{{ children|length }} item{{ children|length|pluralize }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0 align-middle">
|
||||
<thead class="table-primary">
|
||||
<tr>
|
||||
<th style="width: 160px;">Code</th>
|
||||
<th>Description</th>
|
||||
<th style="width: 120px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for child in children %}
|
||||
<tr>
|
||||
<td class="fw-semibold">
|
||||
{{ child.code }}
|
||||
{% if child.is_header %}<span class="badge bg-secondary ms-1">Header</span>{% endif %}
|
||||
</td>
|
||||
<td>{{ child.description|default:"—" }}</td>
|
||||
<td class="text-end">
|
||||
<a class="btn btn-outline-primary btn-sm" href="{% url 'emr:icd10_detail' child.code %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-muted">No subcodes.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
107
emr/templates/emr/icd10_search.html
Normal file
107
emr/templates/emr/icd10_search.html
Normal file
@ -0,0 +1,107 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load humanize custom_filters %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<div>
|
||||
<h1 class="h2">
|
||||
<i class="fas fa-book-medical me-2"></i>ICD10<span class="fw-light">Search</span>
|
||||
</h1>
|
||||
<p class="text-muted">Classification of Diseases, Functioning, and Disability</p>
|
||||
</div>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="panel panel-inverse" data-sortable-id="index-1">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><i class="fas fa-search me-2"></i> {{ _("Search")}}</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<form method="get" class="row gy-2 gx-3 align-items-center mb-4">
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="q" value="{{ query }}" class="form-control" placeholder="Type description or code…">
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<select name="mode" class="form-select">
|
||||
<option value="description" {% if mode == "description" %}selected{% endif %}>Search by description</option>
|
||||
<option value="code" {% if mode == "code" %}selected{% endif %}>Search by code</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-3 d-grid d-sm-block">
|
||||
<button class="btn btn-primary" type="submit">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% if page_obj %}
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0 align-middle">
|
||||
<thead class="table-primary">
|
||||
<tr>
|
||||
<th style="width: 140px;">Code</th>
|
||||
<th>Description</th>
|
||||
<th style="width: 140px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in results %}
|
||||
<tr>
|
||||
<td class="fw-semibold">
|
||||
{{ item.code }}
|
||||
{% if item.is_header %}
|
||||
<span class="badge bg-secondary ms-1">Header</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.description|default:"—" }}
|
||||
{% if item.section_name or item.chapter_name %}
|
||||
<div class="small text-muted mt-1">
|
||||
{% if item.section_name %}Section: {{ item.section_name }}{% endif %}
|
||||
{% if item.section_name and item.chapter_name %} · {% endif %}
|
||||
{% if item.chapter_name %}Chapter: {{ item.chapter_name }}{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a class="btn btn-outline-primary btn-sm" href="{% url 'emr:icd10_detail' item.code %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-muted">No results.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if is_paginated %}
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% elif query %}
|
||||
<div class="alert alert-info">No results found.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -4,7 +4,7 @@
|
||||
{% block title %}{{ problem.problem_name }} | Problem Detail{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<link href="{% static 'plugins/timeline/timeline.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/lity/dist/lity.min.css' %}" rel="stylesheet" />
|
||||
<link href="{% static 'plugins/bootstrap-icons/font/bootstrap-icons.css' %}" rel="stylesheet" />
|
||||
<style>
|
||||
.problem-badge {
|
||||
@ -12,10 +12,10 @@
|
||||
padding: 0.35em 0.65em;
|
||||
}
|
||||
.problem-header {
|
||||
background: linear-gradient(45deg, var(--bs-indigo), var(--bs-purple));
|
||||
background: linear-gradient(90deg, var(--bs-black), var(--bs-secondary));
|
||||
color: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.problem-status-active {
|
||||
@ -71,310 +71,316 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<div>
|
||||
<h1 class="h2">
|
||||
<i class="fas fa-book-medical me-2"></i>Problem<span class="fw-light">Detail</span>
|
||||
</h1>
|
||||
<p class="text-muted">Comprehensive problem information</p>
|
||||
</div>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<a href="{% url 'emr:problem_list' %}" class="btn btn-sm btn-outline-theme me-2">
|
||||
<i class="fa fa-arrow-left me-1"></i> Back to Problem List
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- begin page-header -->
|
||||
<h1 class="page-header">Problem Detail <small>Comprehensive problem information</small></h1>
|
||||
<!-- end page-header -->
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xl-12">
|
||||
<div class="problem-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h2 class="mb-1">{{ problem.problem_name }}</h2>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-3">
|
||||
<i class="bi bi-person-fill me-1"></i> {{ problem.patient.get_full_name }}
|
||||
</span>
|
||||
<span class="me-3">
|
||||
<i class="bi bi-calendar-event me-1"></i> Onset: {{ problem.onset_date|default:"Not specified" }}
|
||||
</span>
|
||||
{% if problem.problem_code %}
|
||||
<span>
|
||||
<i class="bi bi-upc-scan me-1"></i> {{ problem.get_coding_system_display }}: {{ problem.problem_code }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<p class="fw-bold me-2">Status:
|
||||
<span class="badge problem-status-{{ problem.status|lower }}">{{ problem.get_status_display }}</span>
|
||||
</p>
|
||||
<p class="fw-bold me-2">Priority:
|
||||
<span class="badge problem-priority-{{ problem.priority|lower }}">{{ problem.get_priority_display }} Priority</span>
|
||||
</p>
|
||||
<p class="fw-bold me-2">Severity:
|
||||
<span class="badge problem-severity-{{ problem.severity|lower }}">{{ problem.get_severity_display }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end problem header -->
|
||||
|
||||
<!-- begin row -->
|
||||
<div class="row">
|
||||
<!-- begin col-12 -->
|
||||
<div class="col-xl-12">
|
||||
<!-- begin problem header -->
|
||||
<div class="problem-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h2 class="mb-1">{{ problem.problem_name }}</h2>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-3">
|
||||
<i class="bi bi-person-fill me-1"></i> {{ problem.patient.get_full_name }}
|
||||
</span>
|
||||
<span class="me-3">
|
||||
<i class="bi bi-calendar-event me-1"></i> Onset: {{ problem.onset_date|default:"Not specified" }}
|
||||
</span>
|
||||
{% if problem.problem_code %}
|
||||
<span>
|
||||
<i class="bi bi-upc-scan me-1"></i> {{ problem.get_coding_system_display }}: {{ problem.problem_code }}
|
||||
</span>
|
||||
<!-- begin panel -->
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Problem Information</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="{% url 'patients:patient_detail' problem.patient.id %}" class="btn btn-xs btn-outline-secondary me-2">
|
||||
<i class="fa fa-user me-1"></i> Patient Profile
|
||||
</a>
|
||||
{% if problem.status != 'RESOLVED' and problem.status != 'REMISSION' %}
|
||||
<a href="#" class="btn btn-xs btn-outline-success me-2" data-bs-toggle="modal" data-bs-target="#resolveModal">
|
||||
<i class="fa fa-check-circle me-1"></i>Mark as Resolved
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'emr:problem_update' problem.id %}" class="btn btn-xs btn-outline-warning me-2">
|
||||
<i class="fa fa-edit me-1"></i>Edit
|
||||
</a>
|
||||
<a href="{% url 'emr:problem_delete' problem.id %}" class="btn btn-xs btn-outline-danger me-2">
|
||||
<i class="fa fa-trash me-1"></i>Delete
|
||||
</a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<!-- begin col-6 -->
|
||||
<div class="col-md-6">
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Basic Information</h5>
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="35%">Problem ID</th>
|
||||
<td>{{ problem.problem_id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Problem Type</th>
|
||||
<td>{{ problem.get_problem_type_display }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>{{ problem.get_status_display }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Onset Date</th>
|
||||
<td>{{ problem.onset_date|default:"Not specified" }}</td>
|
||||
</tr>
|
||||
{% if problem.status == 'RESOLVED' or problem.status == 'REMISSION' %}
|
||||
<tr>
|
||||
<th>Resolution Date</th>
|
||||
<td>{{ problem.resolution_date }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Duration</th>
|
||||
<td>{{ problem.duration.days }} days</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Body Site</th>
|
||||
<td>{{ problem.body_site|default:"Not specified" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Laterality</th>
|
||||
<td>{{ problem.get_laterality_display|default:"Not specified" }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if problem.clinical_notes %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Clinical Notes</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.clinical_notes|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if problem.patient_concerns %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Patient Concerns</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.patient_concerns|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- end col-6 -->
|
||||
|
||||
<!-- begin col-6 -->
|
||||
<div class="col-md-6">
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Provider Information</h5>
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="35%">Diagnosing Provider</th>
|
||||
<td>{{ problem.diagnosing_provider.get_full_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Managing Provider</th>
|
||||
<td>{{ problem.managing_provider.get_full_name|default:"Not assigned" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Related Encounter</th>
|
||||
<td>
|
||||
{% if problem.related_encounter %}
|
||||
<a href="{% url 'emr:encounter_detail' problem.related_encounter.id %}">
|
||||
{{ problem.related_encounter.encounter_type }} ({{ problem.related_encounter.start_datetime|date:"M d, Y" }})
|
||||
</a>
|
||||
{% else %}
|
||||
Not linked to an encounter
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Verified</th>
|
||||
<td>
|
||||
{% if problem.verified %}
|
||||
<span class="badge bg-success">Verified</span>
|
||||
by {{ problem.verified_by.get_full_name }} on {{ problem.verified_date|date:"M d, Y" }}
|
||||
{% else %}
|
||||
<span class="badge bg-warning">Not Verified</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if problem.treatment_goals %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Treatment Goals</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.treatment_goals|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if problem.outcome_measures %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Outcome Measures</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.outcome_measures|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if problem.status == 'RESOLVED' or problem.status == 'REMISSION' %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Resolution Notes</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.resolution_notes|linebreaks|default:"No resolution notes provided." }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- end col-6 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end panel -->
|
||||
|
||||
<!-- begin related care plans panel -->
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Related Care Plans</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if problem.care_plans.all %}
|
||||
<div class="row">
|
||||
{% for care_plan in problem.care_plans.all %}
|
||||
<div class="col-md-6 col-xl-4 mb-3">
|
||||
<div class="card related-item h-100">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">{{ care_plan.title }}</h5>
|
||||
<span class="badge bg-{{ care_plan.status|lower }}">{{ care_plan.get_status_display }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ care_plan.description|truncatechars:100 }}</p>
|
||||
<div class="progress mb-2" style="height: 10px;">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: {{ care_plan.completion_percentage }}%">{{ care_plan.completion_percentage }}%</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted">{{ care_plan.get_plan_type_display }}</small>
|
||||
<small class="text-muted">{{ care_plan.start_date|date:"M d, Y" }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="{% url 'emr:care_plan_detail' care_plan.id %}" class="btn btn-sm btn-primary">View Care Plan</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No care plans are associated with this problem.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<span class="badge problem-status-{{ problem.status|lower }} me-2">{{ problem.get_status_display }}</span>
|
||||
<span class="badge problem-priority-{{ problem.priority|lower }} me-2">{{ problem.get_priority_display }} Priority</span>
|
||||
<span class="badge problem-severity-{{ problem.severity|lower }}">{{ problem.get_severity_display }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end problem header -->
|
||||
|
||||
<!-- begin panel -->
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Problem Information</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<!-- begin col-6 -->
|
||||
<div class="col-md-6">
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Basic Information</h5>
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="35%">Problem ID</th>
|
||||
<td>{{ problem.problem_id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Problem Type</th>
|
||||
<td>{{ problem.get_problem_type_display }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>{{ problem.get_status_display }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Onset Date</th>
|
||||
<td>{{ problem.onset_date|default:"Not specified" }}</td>
|
||||
</tr>
|
||||
{% if problem.status == 'RESOLVED' or problem.status == 'REMISSION' %}
|
||||
<tr>
|
||||
<th>Resolution Date</th>
|
||||
<td>{{ problem.resolution_date }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Duration</th>
|
||||
<td>{{ problem.duration.days }} days</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>Body Site</th>
|
||||
<td>{{ problem.body_site|default:"Not specified" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Laterality</th>
|
||||
<td>{{ problem.get_laterality_display|default:"Not specified" }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if problem.clinical_notes %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Clinical Notes</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.clinical_notes|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if problem.patient_concerns %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Patient Concerns</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.patient_concerns|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end related care plans panel -->
|
||||
|
||||
<!-- begin clinical notes panel -->
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Related Clinical Notes</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
<!-- end col-6 -->
|
||||
|
||||
<!-- begin col-6 -->
|
||||
<div class="col-md-6">
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Provider Information</h5>
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th width="35%">Diagnosing Provider</th>
|
||||
<td>{{ problem.diagnosing_provider.get_full_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Managing Provider</th>
|
||||
<td>{{ problem.managing_provider.get_full_name|default:"Not assigned" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Related Encounter</th>
|
||||
<td>
|
||||
{% if problem.related_encounter %}
|
||||
<a href="{% url 'emr:encounter_detail' problem.related_encounter.id %}">
|
||||
{{ problem.related_encounter.encounter_type }} ({{ problem.related_encounter.start_datetime|date:"M d, Y" }})
|
||||
</a>
|
||||
{% else %}
|
||||
Not linked to an encounter
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Verified</th>
|
||||
<td>
|
||||
{% if problem.verified %}
|
||||
<span class="badge bg-success">Verified</span>
|
||||
by {{ problem.verified_by.get_full_name }} on {{ problem.verified_date|date:"M d, Y" }}
|
||||
{% else %}
|
||||
<span class="badge bg-warning">Not Verified</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if problem.treatment_goals %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Treatment Goals</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.treatment_goals|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if problem.outcome_measures %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Outcome Measures</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.outcome_measures|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if problem.status == 'RESOLVED' or problem.status == 'REMISSION' %}
|
||||
<div class="mb-4">
|
||||
<h5 class="mb-3">Resolution Notes</h5>
|
||||
<div class="p-3 bg-light rounded">
|
||||
{{ problem.resolution_notes|linebreaks|default:"No resolution notes provided." }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- end col-6 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end panel -->
|
||||
|
||||
<!-- begin related care plans panel -->
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Related Care Plans</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if problem.care_plans.all %}
|
||||
<div class="row">
|
||||
{% for care_plan in problem.care_plans.all %}
|
||||
<div class="col-md-6 col-xl-4 mb-3">
|
||||
<div class="card related-item h-100">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">{{ care_plan.title }}</h5>
|
||||
<span class="badge bg-{{ care_plan.status|lower }}">{{ care_plan.get_status_display }}</span>
|
||||
<div class="panel-body">
|
||||
{% if problem.related_clinical_notes.all %}
|
||||
<div class="timeline">
|
||||
{% for note in problem.related_clinical_notes.all %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-time">
|
||||
{{ note.note_datetime|date:"M d, Y" }} <small>{{ note.note_datetime|time:"h:i A" }}</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ care_plan.description|truncatechars:100 }}</p>
|
||||
<div class="progress mb-2" style="height: 10px;">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: {{ care_plan.completion_percentage }}%">{{ care_plan.completion_percentage }}%</div>
|
||||
</div>
|
||||
<div class="timeline-icon">
|
||||
<a href="javascript:;"> </a>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted">{{ care_plan.get_plan_type_display }}</small>
|
||||
<small class="text-muted">{{ care_plan.start_date|date:"M d, Y" }}</small>
|
||||
<h5>{{ note.title }}</h5>
|
||||
<span class="badge bg-{{ note.status|lower }}">{{ note.get_status_display }}</span>
|
||||
</div>
|
||||
<p class="mb-2">{{ note.content|truncatechars:200 }}</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted">{{ note.get_note_type_display }} by {{ note.author.get_full_name }}</small>
|
||||
<a href="{% url 'emr:clinical_note_detail' note.id %}" class="btn btn-sm btn-primary">View Note</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="{% url 'emr:care_plan_detail' care_plan.id %}" class="btn btn-sm btn-primary">View Care Plan</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No care plans are associated with this problem.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- end related care plans panel -->
|
||||
|
||||
<!-- begin clinical notes panel -->
|
||||
<div class="panel panel-inverse">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">Related Clinical Notes</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if problem.related_clinical_notes.all %}
|
||||
<div class="timeline">
|
||||
{% for note in problem.related_clinical_notes.all %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-time">
|
||||
{{ note.note_datetime|date:"M d, Y" }} <small>{{ note.note_datetime|time:"h:i A" }}</small>
|
||||
</div>
|
||||
<div class="timeline-icon">
|
||||
<a href="javascript:;"> </a>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5>{{ note.title }}</h5>
|
||||
<span class="badge bg-{{ note.status|lower }}">{{ note.get_status_display }}</span>
|
||||
</div>
|
||||
<p class="mb-2">{{ note.content|truncatechars:200 }}</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted">{{ note.get_note_type_display }} by {{ note.author.get_full_name }}</small>
|
||||
<a href="{% url 'emr:clinical_note_detail' note.id %}" class="btn btn-sm btn-primary">View Note</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No clinical notes are associated with this problem.
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<i class="fa fa-info-circle me-2"></i> No clinical notes are associated with this problem.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- end clinical notes panel -->
|
||||
|
||||
</div>
|
||||
<!-- end clinical notes panel -->
|
||||
|
||||
<!-- begin action buttons -->
|
||||
<div class="d-flex justify-content-between mt-3">
|
||||
<div>
|
||||
<a href="{% url 'emr:problem_list' %}" class="btn btn-secondary me-2">
|
||||
<i class="fa fa-arrow-left me-1"></i> Back to Problem List
|
||||
</a>
|
||||
<a href="{% url 'patients:patient_detail' problem.patient.id %}" class="btn btn-info">
|
||||
<i class="fa fa-user me-1"></i> Patient Profile
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
{% if problem.status != 'RESOLVED' and problem.status != 'REMISSION' %}
|
||||
<a href="#" class="btn btn-success me-2" data-bs-toggle="modal" data-bs-target="#resolveModal">
|
||||
<i class="fa fa-check-circle me-1"></i> Mark as Resolved
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'emr:problem_update' problem.id %}" class="btn btn-primary me-2">
|
||||
<i class="fa fa-edit me-1"></i> Edit Problem
|
||||
</a>
|
||||
<a href="{% url 'emr:problem_delete' problem.id %}" class="btn btn-danger">
|
||||
<i class="fa fa-trash me-1"></i> Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end action buttons -->
|
||||
</div>
|
||||
<!-- end col-12 -->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
|
||||
<!-- begin resolve modal -->
|
||||
<div class="modal fade" id="resolveModal" tabindex="-1" aria-labelledby="resolveModalLabel" aria-hidden="true">
|
||||
@ -415,6 +421,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{% static 'plugins/lity/dist/lity.min.js' %}"></script>
|
||||
<script src="{% static 'plugins/moment/min/moment.min.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
@ -4,165 +4,174 @@
|
||||
{% block title %}Problem List - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<div>
|
||||
<h1 class="h2">
|
||||
<i class="fas fa-list me-2"></i>Problem<span class="fw-light">Management</span>
|
||||
</h1>
|
||||
<p class="text-muted">Comprehensive list of Diagnosis, Symptoms and more.</p>
|
||||
</div>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="panel panel-inverse" data-sortable-id="index-1">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<i class="fas fa-list me-2"></i>Problem List Management
|
||||
</h4>
|
||||
<div class="panel-heading-btn">
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||
<a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<!-- Filters -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2">
|
||||
<select name="problem_type" class="form-select">
|
||||
<option value="">All Types</option>
|
||||
{% for value, label in problem_types %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="status" class="form-select">
|
||||
<option value="">All Status</option>
|
||||
{% for value, label in problem_statuses %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="priority" class="form-select">
|
||||
<option value="">All Priorities</option>
|
||||
{% for value, label in priorities %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" name="search" class="form-control" placeholder="Search patients, problems, codes...">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Problems Table -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title mb-0">
|
||||
<i class="fas fa-list me-2"></i>Problem List Management
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- Filters -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2">
|
||||
<select name="problem_type" class="form-select">
|
||||
<option value="">All Types</option>
|
||||
{% for value, label in problem_types %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="status" class="form-select">
|
||||
<option value="">All Status</option>
|
||||
{% for value, label in problem_statuses %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<select name="priority" class="form-select">
|
||||
<option value="">All Priorities</option>
|
||||
{% for value, label in priorities %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="text" name="search" class="form-control" placeholder="Search patients, problems, codes...">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Problems Table -->
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Patient</th>
|
||||
<th>Problem</th>
|
||||
<th>Type</th>
|
||||
<th>Priority</th>
|
||||
<th>Status</th>
|
||||
<th>Onset Date</th>
|
||||
<th>Diagnosed By</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for problem in problems %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ problem.patient.get_full_name }}</strong><br>
|
||||
<small class="text-muted">
|
||||
MRN: {{ problem.patient.mrn }} •
|
||||
{{ problem.patient.age }}y {{ problem.patient.get_gender_display }}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0 align-middle">
|
||||
<thead class="table-primary">
|
||||
<tr>
|
||||
<th>Patient</th>
|
||||
<th>Problem</th>
|
||||
<th>Type</th>
|
||||
<th>Priority</th>
|
||||
<th>Status</th>
|
||||
<th>Onset Date</th>
|
||||
<th>Diagnosed By</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for problem in problems %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ problem.patient.get_full_name }}</strong><br>
|
||||
<small class="text-muted">
|
||||
MRN: {{ problem.patient.mrn }} •
|
||||
{{ problem.patient.age }}y {{ problem.patient.get_gender_display }}
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ problem.problem_name }}</strong>
|
||||
{% if problem.problem_code %}
|
||||
<br><small class="text-muted">Code: {{ problem.problem_code }}</small>
|
||||
{% endif %}
|
||||
{% if problem.description %}
|
||||
<br><small class="text-muted">{{ problem.description|truncatechars:50 }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ problem.get_problem_type_display }}</td>
|
||||
<td>
|
||||
{% if problem.priority == 'HIGH' %}
|
||||
<span class="badge bg-danger">High</span>
|
||||
{% elif problem.priority == 'MEDIUM' %}
|
||||
<span class="badge bg-warning">Medium</span>
|
||||
{% else %}
|
||||
<span class="badge bg-info">Low</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if problem.status == 'ACTIVE' %}
|
||||
<span class="badge bg-success">Active</span>
|
||||
{% elif problem.status == 'RESOLVED' %}
|
||||
<span class="badge bg-secondary">Resolved</span>
|
||||
{% elif problem.status == 'INACTIVE' %}
|
||||
<span class="badge bg-secondary">Inactive</span>
|
||||
{% elif problem.status == 'RULED_OUT' %}
|
||||
<span class="badge bg-danger">Ruled Out</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ problem.onset_date|date:"M d, Y" }}
|
||||
{% if problem.resolution_date %}
|
||||
<br><small class="text-muted">
|
||||
Resolved: {{ problem.resolution_date|date:"M d, Y" }}
|
||||
</small>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ problem.problem_name }}</strong>
|
||||
{% if problem.problem_code %}
|
||||
<br><small class="text-muted">Code: {{ problem.problem_code }}</small>
|
||||
{% endif %}
|
||||
{% if problem.description %}
|
||||
<br><small class="text-muted">{{ problem.description|truncatechars:50 }}</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ problem.get_problem_type_display }}</td>
|
||||
<td>
|
||||
{% if problem.priority == 'HIGH' %}
|
||||
<span class="badge bg-danger">High</span>
|
||||
{% elif problem.priority == 'MEDIUM' %}
|
||||
<span class="badge bg-warning">Medium</span>
|
||||
{% else %}
|
||||
<span class="badge bg-info">Low</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ problem.diagnosing_provider.get_full_name }}
|
||||
{% if problem.managing_provider and problem.managing_provider != problem.diagnosing_provider %}
|
||||
<br><small class="text-muted">
|
||||
Managing: {{ problem.managing_provider.get_full_name }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a href="{% url 'emr:problem_detail' problem.id %}" class="btn btn-outline-primary" title="View Details">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
{% if problem.status == 'ACTIVE' %}
|
||||
<span class="badge bg-success">Active</span>
|
||||
{% elif problem.status == 'RESOLVED' %}
|
||||
<span class="badge bg-secondary">Resolved</span>
|
||||
{% elif problem.status == 'INACTIVE' %}
|
||||
<span class="badge bg-secondary">Inactive</span>
|
||||
{% elif problem.status == 'RULED_OUT' %}
|
||||
<span class="badge bg-danger">Ruled Out</span>
|
||||
<button class="btn btn-outline-success"
|
||||
title="Mark Resolved"
|
||||
hx-post="{% url 'emr:resolve_problem' problem.id %}"
|
||||
hx-confirm="Mark this problem as resolved?"
|
||||
hx-swap="none"
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
||||
<i class="fas fa-check"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ problem.onset_date|date:"M d, Y" }}
|
||||
{% if problem.resolution_date %}
|
||||
<br><small class="text-muted">
|
||||
Resolved: {{ problem.resolution_date|date:"M d, Y" }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ problem.diagnosing_provider.get_full_name }}
|
||||
{% if problem.managing_provider and problem.managing_provider != problem.diagnosing_provider %}
|
||||
<br><small class="text-muted">
|
||||
Managing: {{ problem.managing_provider.get_full_name }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-primary" title="View Details">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
{% if problem.status == 'ACTIVE' %}
|
||||
<button class="btn btn-outline-success"
|
||||
title="Mark Resolved"
|
||||
hx-post="{% url 'emr:resolve_problem' problem.id %}"
|
||||
hx-confirm="Mark this problem as resolved?"
|
||||
hx-swap="none">
|
||||
<i class="fas fa-check"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
<button class="btn btn-outline-info" title="Care Plan">
|
||||
<i class="fas fa-clipboard-list"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" title="Notes">
|
||||
<i class="fas fa-sticky-note"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-4">
|
||||
<i class="fas fa-list fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">No problems found</h5>
|
||||
<p class="text-muted">No problems match your current filters.</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-4">
|
||||
<i class="fas fa-list fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">No problems found</h5>
|
||||
<p class="text-muted">No problems match your current filters.</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
{% include 'partial/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -29,6 +29,9 @@ urlpatterns = [
|
||||
path('care-plans/<int:pk>/', views.CarePlanDetailView.as_view(), name='care_plan_detail'),
|
||||
path('care-plans/<int:pk>/update/', views.CarePlanUpdateView.as_view(), name='care_plan_update'),
|
||||
path('care-plans/<int:pk>/delete/', views.CarePlanDeleteView.as_view(), name='care_plan_delete'),
|
||||
path('care-plans/<int:pk>/progress/', views.CarePlanProgressUpdateView.as_view(), name='care_plan_progress_update'),
|
||||
path('care-plans/approve/<int:pk>/', views.approve_care_plan, name='approve_care_plan'),
|
||||
path('care-plans/complete/<int:pk>/', views.complete_care_plan, name='complete_care_plan'),
|
||||
|
||||
path('notes/', views.ClinicalNoteListView.as_view(), name='clinical_note_list'),
|
||||
path('notes/<int:pk>/', views.ClinicalNoteDetailView.as_view(), name='clinical_note_detail'),
|
||||
@ -49,6 +52,10 @@ urlpatterns = [
|
||||
path('encounter/<int:encounter_id>/status/', views.update_encounter_status, name='update_encounter_status'),
|
||||
path('note/<int:note_id>/sign/', views.sign_note, name='sign_note'),
|
||||
path('problem/<int:problem_id>/resolve/', views.resolve_problem, name='resolve_problem'),
|
||||
|
||||
|
||||
path("icd10/", views.Icd10SearchView.as_view(), name="icd10_search"),
|
||||
path("icd10/<str:code>/", views.Icd10DetailView.as_view(), name="icd10_detail"),
|
||||
|
||||
# API endpoints
|
||||
# path('api/', include('emr.api.urls')),
|
||||
|
||||
17
emr/utils.py
Normal file
17
emr/utils.py
Normal file
@ -0,0 +1,17 @@
|
||||
from django.conf import settings
|
||||
from python_icd10.icd10_warehouse import ICD10Warehouse
|
||||
|
||||
icd10 = ICD10Warehouse(settings.BASE_DIR)
|
||||
|
||||
|
||||
|
||||
def search_by_description(description):
|
||||
return icd10.search_by_description(description)
|
||||
|
||||
|
||||
def search_by_code(code):
|
||||
return icd10.search_by_code(code)
|
||||
|
||||
|
||||
def get_by_code(code):
|
||||
return icd10.get_by_code(code)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user