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.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
import uuid
|
import uuid
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
@ -386,6 +387,21 @@ class Migration(migrations.Migration):
|
|||||||
verbose_name="superuser status",
|
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",
|
"first_name",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
@ -424,67 +440,15 @@ class Migration(migrations.Migration):
|
|||||||
"user_id",
|
"user_id",
|
||||||
models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
|
models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
|
||||||
),
|
),
|
||||||
(
|
("email", models.EmailField(max_length=254, unique=True)),
|
||||||
"username",
|
("force_password_change", models.BooleanField(default=False)),
|
||||||
models.CharField(
|
("password_expires_at", models.DateTimeField(blank=True, null=True)),
|
||||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
("failed_login_attempts", models.PositiveIntegerField(default=0)),
|
||||||
max_length=150,
|
("locked_until", models.DateTimeField(blank=True, null=True)),
|
||||||
unique=True,
|
("two_factor_enabled", models.BooleanField(default=False)),
|
||||||
),
|
("max_concurrent_sessions", models.PositiveIntegerField(default=3)),
|
||||||
),
|
("session_timeout_minutes", models.PositiveIntegerField(default=30)),
|
||||||
("email", models.EmailField(blank=True, max_length=254, null=True)),
|
("last_password_change", models.DateTimeField(blank=True, 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",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
"groups",
|
"groups",
|
||||||
models.ManyToManyField(
|
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
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -20,7 +20,6 @@ class Migration(migrations.Migration):
|
|||||||
model_name="user",
|
model_name="user",
|
||||||
name="tenant",
|
name="tenant",
|
||||||
field=models.ForeignKey(
|
field=models.ForeignKey(
|
||||||
help_text="Organization tenant",
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
related_name="users",
|
related_name="users",
|
||||||
to="core.tenant",
|
to="core.tenant",
|
||||||
@ -86,12 +85,6 @@ class Migration(migrations.Migration):
|
|||||||
fields=["tenant", "username"], name="accounts_us_tenant__d92906_idx"
|
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(
|
migrations.AddIndex(
|
||||||
model_name="user",
|
model_name="user",
|
||||||
index=models.Index(
|
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):
|
class User(AbstractUser):
|
||||||
"""
|
user_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
|
||||||
Minimal auth user for a multi-tenant app:
|
email = models.EmailField(unique=True)
|
||||||
- Authentication core (from AbstractUser)
|
tenant = models.ForeignKey('core.Tenant', on_delete=models.PROTECT, related_name='users')
|
||||||
- Tenant link
|
force_password_change = models.BooleanField(default=False)
|
||||||
- Security/session controls (lockout, password expiry, 2FA flag, session caps)
|
password_expires_at = models.DateTimeField(blank=True, null=True)
|
||||||
Everything else lives on hr.Employee.
|
failed_login_attempts = models.PositiveIntegerField(default=0)
|
||||||
"""
|
locked_until = models.DateTimeField(blank=True, null=True)
|
||||||
|
two_factor_enabled = models.BooleanField(default=False)
|
||||||
# Stable internal UUID
|
max_concurrent_sessions = models.PositiveIntegerField(default=3)
|
||||||
user_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
session_timeout_minutes = models.PositiveIntegerField(default=30)
|
||||||
|
last_password_change = models.DateTimeField(blank=True, null=True)
|
||||||
# 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'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'accounts_user'
|
db_table = 'accounts_user'
|
||||||
@ -431,15 +377,15 @@ class User(AbstractUser):
|
|||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['tenant', 'email']),
|
models.Index(fields=['tenant', 'email']),
|
||||||
models.Index(fields=['tenant', 'username']),
|
models.Index(fields=['tenant', 'username']),
|
||||||
models.Index(fields=['tenant', 'user_id']),
|
|
||||||
models.Index(fields=['tenant', 'is_active']),
|
models.Index(fields=['tenant', 'is_active']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
full = super().get_full_name().strip()
|
full = super().get_full_name().strip()
|
||||||
return f"{full or self.username} (tenant={self.tenant_id})"
|
return f"{full or self.username}"
|
||||||
|
|
||||||
# ---- Security helpers ----
|
# ---- Security helpers ----
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_account_locked(self) -> bool:
|
def is_account_locked(self) -> bool:
|
||||||
return bool(self.locked_until and timezone.now() < self.locked_until)
|
return bool(self.locked_until and timezone.now() < self.locked_until)
|
||||||
@ -477,13 +423,22 @@ class User(AbstractUser):
|
|||||||
self.failed_login_attempts = 0
|
self.failed_login_attempts = 0
|
||||||
self.save(update_fields=['failed_login_attempts'])
|
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):
|
class TwoFactorDevice(models.Model):
|
||||||
"""
|
class DeviceType(models.TextChoices):
|
||||||
Two-factor authentication devices for users.
|
TOTP = 'TOTP', 'Time-based OTP (Authenticator App)'
|
||||||
"""
|
SMS = 'SMS', 'SMS'
|
||||||
|
EMAIL = 'EMAIL', 'Email'
|
||||||
|
HARDWARE = 'HARDWARE', 'Hardware Token'
|
||||||
|
BACKUP = 'BACKUP', 'Backup Codes'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# User relationship
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -501,16 +456,7 @@ class TwoFactorDevice(models.Model):
|
|||||||
max_length=100,
|
max_length=100,
|
||||||
help_text='Device name'
|
help_text='Device name'
|
||||||
)
|
)
|
||||||
device_type = models.CharField(
|
device_type = models.CharField(max_length=20,choices=DeviceType.choices)
|
||||||
max_length=20,
|
|
||||||
choices=[
|
|
||||||
('TOTP', 'Time-based OTP (Authenticator App)'),
|
|
||||||
('SMS', 'SMS'),
|
|
||||||
('EMAIL', 'Email'),
|
|
||||||
('HARDWARE', 'Hardware Token'),
|
|
||||||
('BACKUP', 'Backup Codes'),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Device Configuration
|
# Device Configuration
|
||||||
secret_key = models.CharField(
|
secret_key = models.CharField(
|
||||||
@ -575,6 +521,16 @@ class SocialAccount(models.Model):
|
|||||||
"""
|
"""
|
||||||
Social authentication accounts linked to users.
|
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 relationship
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
@ -584,20 +540,7 @@ class SocialAccount(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Provider Information
|
# Provider Information
|
||||||
provider = models.CharField(
|
provider = models.CharField(max_length=50,choices=Provider.choices)
|
||||||
max_length=50,
|
|
||||||
choices=[
|
|
||||||
('GOOGLE', 'Google'),
|
|
||||||
('MICROSOFT', 'Microsoft'),
|
|
||||||
('APPLE', 'Apple'),
|
|
||||||
('FACEBOOK', 'Facebook'),
|
|
||||||
('LINKEDIN', 'LinkedIn'),
|
|
||||||
('GITHUB', 'GitHub'),
|
|
||||||
('OKTA', 'Okta'),
|
|
||||||
('SAML', 'SAML'),
|
|
||||||
('LDAP', 'LDAP'),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
provider_id = models.CharField(
|
provider_id = models.CharField(
|
||||||
max_length=200,
|
max_length=200,
|
||||||
help_text='Provider user ID'
|
help_text='Provider user ID'
|
||||||
@ -673,6 +616,20 @@ class UserSession(models.Model):
|
|||||||
"""
|
"""
|
||||||
User session tracking for security and audit purposes.
|
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 relationship
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
@ -703,12 +660,7 @@ class UserSession(models.Model):
|
|||||||
)
|
)
|
||||||
device_type = models.CharField(
|
device_type = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=DeviceType.choices,
|
||||||
('DESKTOP', 'Desktop'),
|
|
||||||
('MOBILE', 'Mobile'),
|
|
||||||
('TABLET', 'Tablet'),
|
|
||||||
('UNKNOWN', 'Unknown'),
|
|
||||||
],
|
|
||||||
default='UNKNOWN'
|
default='UNKNOWN'
|
||||||
)
|
)
|
||||||
browser = models.CharField(
|
browser = models.CharField(
|
||||||
@ -751,14 +703,8 @@ class UserSession(models.Model):
|
|||||||
)
|
)
|
||||||
login_method = models.CharField(
|
login_method = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=LoginMethod.choices,
|
||||||
('PASSWORD', 'Password'),
|
default=LoginMethod.PASSWORD
|
||||||
('TWO_FACTOR', 'Two Factor'),
|
|
||||||
('SOCIAL', 'Social Login'),
|
|
||||||
('SSO', 'Single Sign-On'),
|
|
||||||
('API_KEY', 'API Key'),
|
|
||||||
],
|
|
||||||
default='PASSWORD'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Timestamps
|
# Timestamps
|
||||||
|
|||||||
@ -8,34 +8,28 @@ from hr.models import Employee
|
|||||||
from .models import User
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User, dispatch_uid="create_employee_on_user_create_simple")
|
||||||
def create_employee_for_user(sender, instance: User, created: bool, **kwargs):
|
def create_employee_on_user_create(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.
|
|
||||||
"""
|
|
||||||
if not created:
|
if not created:
|
||||||
return
|
return
|
||||||
|
|
||||||
def _make_employee():
|
def _make():
|
||||||
# Generate a readable employee_number from username/email, unique within tenant
|
tenant = getattr(instance, "tenant", None)
|
||||||
base = (instance.username or (instance.email or 'user')).split('@')[0]
|
email = (instance.email or "").strip()
|
||||||
candidate = slugify(base)[:16] or 'emp'
|
|
||||||
suffix = 1
|
# If already linked OR same email exists in this tenant, skip
|
||||||
emp_no = candidate
|
if Employee.objects.filter(user=instance).exists():
|
||||||
while Employee.objects.filter(tenant=instance.tenant, employee_number=emp_no).exists():
|
return
|
||||||
suffix += 1
|
if email and Employee.objects.filter(tenant=tenant, email__iexact=email).exists():
|
||||||
emp_no = f"{candidate}-{suffix}"
|
return
|
||||||
|
|
||||||
# Create with basic info mirrored from User
|
|
||||||
Employee.objects.create(
|
Employee.objects.create(
|
||||||
tenant=instance.tenant,
|
tenant=tenant,
|
||||||
user=instance,
|
user=instance,
|
||||||
employee_number=emp_no,
|
first_name=instance.first_name or "",
|
||||||
first_name=instance.first_name or '',
|
last_name=instance.last_name or "",
|
||||||
last_name=instance.last_name or '',
|
email=email,
|
||||||
email=instance.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)
|
||||||
transaction.on_commit(_make_employee)
|
|
||||||
@ -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.core.validators
|
||||||
import django.db.models.deletion
|
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
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
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.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
@ -700,6 +700,7 @@ class Migration(migrations.Migration):
|
|||||||
blank=True,
|
blank=True,
|
||||||
default=list,
|
default=list,
|
||||||
help_text="Acceptable days of week (0=Monday, 6=Sunday)",
|
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
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
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 request model for scheduling patient appointments.
|
||||||
"""
|
"""
|
||||||
APPOINTMENT_TYPE_CHOICES = [
|
|
||||||
('CONSULTATION', 'Consultation'),
|
class AppointmentType(models.TextChoices):
|
||||||
('FOLLOW_UP', 'Follow-up'),
|
CONSULTATION = 'CONSULTATION', 'Consultation'
|
||||||
('PROCEDURE', 'Procedure'),
|
FOLLOW_UP = 'FOLLOW_UP', 'Follow-up'
|
||||||
('SURGERY', 'Surgery'),
|
PROCEDURE = 'PROCEDURE', 'Procedure'
|
||||||
('DIAGNOSTIC', 'Diagnostic'),
|
SURGERY = 'SURGERY', 'Surgery'
|
||||||
('THERAPY', 'Therapy'),
|
DIAGNOSTIC = 'DIAGNOSTIC', 'Diagnostic'
|
||||||
('VACCINATION', 'Vaccination'),
|
THERAPY = 'THERAPY', 'Therapy'
|
||||||
('SCREENING', 'Screening'),
|
VACCINATION = 'VACCINATION', 'Vaccination'
|
||||||
('EMERGENCY', 'Emergency'),
|
SCREENING = 'SCREENING', 'Screening'
|
||||||
('TELEMEDICINE', 'Telemedicine'),
|
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||||
('OTHER', 'Other'),
|
TELEMEDICINE = 'TELEMEDICINE', 'Telemedicine'
|
||||||
]
|
OTHER = 'OTHER', 'Other'
|
||||||
SPECIALTY_CHOICES = [
|
|
||||||
('FAMILY_MEDICINE', 'Family Medicine'),
|
class Specialty(models.TextChoices):
|
||||||
('INTERNAL_MEDICINE', 'Internal Medicine'),
|
FAMILY_MEDICINE = 'FAMILY_MEDICINE', 'Family Medicine'
|
||||||
('PEDIATRICS', 'Pediatrics'),
|
INTERNAL_MEDICINE = 'INTERNAL_MEDICINE', 'Internal Medicine'
|
||||||
('CARDIOLOGY', 'Cardiology'),
|
PEDIATRICS = 'PEDIATRICS', 'Pediatrics'
|
||||||
('DERMATOLOGY', 'Dermatology'),
|
CARDIOLOGY = 'CARDIOLOGY', 'Cardiology'
|
||||||
('ENDOCRINOLOGY', 'Endocrinology'),
|
DERMATOLOGY = 'DERMATOLOGY', 'Dermatology'
|
||||||
('GASTROENTEROLOGY', 'Gastroenterology'),
|
ENDOCRINOLOGY = 'ENDOCRINOLOGY', 'Endocrinology'
|
||||||
('NEUROLOGY', 'Neurology'),
|
GASTROENTEROLOGY = 'GASTROENTEROLOGY', 'Gastroenterology'
|
||||||
('ONCOLOGY', 'Oncology'),
|
NEUROLOGY = 'NEUROLOGY', 'Neurology'
|
||||||
('ORTHOPEDICS', 'Orthopedics'),
|
ONCOLOGY = 'ONCOLOGY', 'Oncology'
|
||||||
('PSYCHIATRY', 'Psychiatry'),
|
ORTHOPEDICS = 'ORTHOPEDICS', 'Orthopedics'
|
||||||
('RADIOLOGY', 'Radiology'),
|
PSYCHIATRY = 'PSYCHIATRY', 'Psychiatry'
|
||||||
('SURGERY', 'Surgery'),
|
RADIOLOGY = 'RADIOLOGY', 'Radiology'
|
||||||
('UROLOGY', 'Urology'),
|
SURGERY = 'SURGERY', 'Surgery'
|
||||||
('GYNECOLOGY', 'Gynecology'),
|
UROLOGY = 'UROLOGY', 'Urology'
|
||||||
('OPHTHALMOLOGY', 'Ophthalmology'),
|
GYNECOLOGY = 'GYNECOLOGY', 'Gynecology'
|
||||||
('ENT', 'Ear, Nose & Throat'),
|
OPHTHALMOLOGY = 'OPHTHALMOLOGY', 'Ophthalmology'
|
||||||
('EMERGENCY', 'Emergency Medicine'),
|
ENT = 'ENT', 'Ear, Nose & Throat'
|
||||||
('OTHER', 'Other'),
|
EMERGENCY = 'EMERGENCY', 'Emergency Medicine'
|
||||||
]
|
OTHER = 'OTHER', 'Other'
|
||||||
PRIORITY_CHOICES=[
|
|
||||||
('ROUTINE', 'Routine'),
|
class Priority(models.TextChoices):
|
||||||
('URGENT', 'Urgent'),
|
ROUTINE = 'ROUTINE', 'Routine'
|
||||||
('STAT', 'STAT'),
|
URGENT = 'URGENT', 'Urgent'
|
||||||
('EMERGENCY', 'Emergency'),
|
STAT = 'STAT', 'STAT'
|
||||||
]
|
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||||
STATUS_CHOICES=[
|
|
||||||
('PENDING', 'Pending'),
|
class AppointmentStatus(models.TextChoices):
|
||||||
('SCHEDULED', 'Scheduled'),
|
PENDING = 'PENDING', 'Pending'
|
||||||
('CONFIRMED', 'Confirmed'),
|
SCHEDULED = 'SCHEDULED', 'Scheduled'
|
||||||
('CHECKED_IN', 'Checked In'),
|
CONFIRMED = 'CONFIRMED', 'Confirmed'
|
||||||
('IN_PROGRESS', 'In Progress'),
|
CHECKED_IN = 'CHECKED_IN', 'Checked In'
|
||||||
('COMPLETED', 'Completed'),
|
IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
|
||||||
('CANCELLED', 'Cancelled'),
|
COMPLETED = 'COMPLETED', 'Completed'
|
||||||
('NO_SHOW', 'No Show'),
|
CANCELLED = 'CANCELLED', 'Cancelled'
|
||||||
('RESCHEDULED', 'Rescheduled'),
|
NO_SHOW = 'NO_SHOW', 'No Show'
|
||||||
]
|
RESCHEDULED = 'RESCHEDULED', 'Rescheduled'
|
||||||
TELEMEDICINE_PLATFORM_CHOICES=[
|
|
||||||
('ZOOM', 'Zoom'),
|
class TelemedicinePlatform(models.TextChoices):
|
||||||
('TEAMS', 'Microsoft Teams'),
|
ZOOM = 'ZOOM', 'Zoom'
|
||||||
('WEBEX', 'Cisco Webex'),
|
TEAMS = 'TEAMS', 'Microsoft Teams'
|
||||||
('DOXY', 'Doxy.me'),
|
WEBEX = 'WEBEX', 'Cisco Webex'
|
||||||
('CUSTOM', 'Custom Platform'),
|
DOXY = 'DOXY', 'Doxy.me'
|
||||||
('OTHER', 'Other'),
|
CUSTOM = 'CUSTOM', 'Custom Platform'
|
||||||
]
|
OTHER = 'OTHER', 'Other'
|
||||||
|
|
||||||
# Basic Identifiers
|
# Basic Identifiers
|
||||||
request_id = models.UUIDField(
|
request_id = models.UUIDField(
|
||||||
default=uuid.uuid4,
|
default=uuid.uuid4,
|
||||||
@ -110,13 +111,13 @@ class AppointmentRequest(models.Model):
|
|||||||
# Appointment Details
|
# Appointment Details
|
||||||
appointment_type = models.CharField(
|
appointment_type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=APPOINTMENT_TYPE_CHOICES,
|
choices=AppointmentType.choices,
|
||||||
help_text='Type of appointment'
|
help_text='Type of appointment'
|
||||||
)
|
)
|
||||||
|
|
||||||
specialty = models.CharField(
|
specialty = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
choices=SPECIALTY_CHOICES,
|
choices=Specialty.choices,
|
||||||
help_text='Medical specialty'
|
help_text='Medical specialty'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -159,8 +160,8 @@ class AppointmentRequest(models.Model):
|
|||||||
# Priority and Urgency
|
# Priority and Urgency
|
||||||
priority = models.CharField(
|
priority = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=PRIORITY_CHOICES,
|
choices=Priority.choices,
|
||||||
default='ROUTINE',
|
default=Priority.ROUTINE,
|
||||||
help_text='Appointment priority'
|
help_text='Appointment priority'
|
||||||
)
|
)
|
||||||
urgency_score = models.PositiveIntegerField(
|
urgency_score = models.PositiveIntegerField(
|
||||||
@ -204,8 +205,8 @@ class AppointmentRequest(models.Model):
|
|||||||
# Status and Workflow
|
# Status and Workflow
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=STATUS_CHOICES,
|
choices=AppointmentStatus.choices,
|
||||||
default='PENDING',
|
default=AppointmentStatus.PENDING,
|
||||||
help_text='Appointment status'
|
help_text='Appointment status'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -242,7 +243,7 @@ class AppointmentRequest(models.Model):
|
|||||||
)
|
)
|
||||||
telemedicine_platform = models.CharField(
|
telemedicine_platform = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=TELEMEDICINE_PLATFORM_CHOICES,
|
choices=TelemedicinePlatform.choices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Telemedicine platform'
|
help_text='Telemedicine platform'
|
||||||
@ -398,13 +399,14 @@ class SlotAvailability(models.Model):
|
|||||||
"""
|
"""
|
||||||
Provider availability slots for appointment scheduling.
|
Provider availability slots for appointment scheduling.
|
||||||
"""
|
"""
|
||||||
AVAILABILITY_TYPE_CHOICES=[
|
|
||||||
('REGULAR', 'Regular Hours'),
|
class AvailabilityType(models.TextChoices):
|
||||||
('EXTENDED', 'Extended Hours'),
|
REGULAR = 'REGULAR', 'Regular Hours'
|
||||||
('EMERGENCY', 'Emergency'),
|
EXTENDED = 'EXTENDED', 'Extended Hours'
|
||||||
('ON_CALL', 'On Call'),
|
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||||
('TELEMEDICINE', 'Telemedicine Only'),
|
ON_CALL = 'ON_CALL', 'On Call'
|
||||||
]
|
TELEMEDICINE = 'TELEMEDICINE', 'Telemedicine Only'
|
||||||
|
|
||||||
# Tenant relationship
|
# Tenant relationship
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
'core.Tenant',
|
'core.Tenant',
|
||||||
@ -446,8 +448,8 @@ class SlotAvailability(models.Model):
|
|||||||
# Availability Type
|
# Availability Type
|
||||||
availability_type = models.CharField(
|
availability_type = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=AVAILABILITY_TYPE_CHOICES,
|
choices=AvailabilityType.choices,
|
||||||
default='REGULAR',
|
default=AvailabilityType.REGULAR,
|
||||||
help_text='Type of availability'
|
help_text='Type of availability'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -588,13 +590,14 @@ class WaitingQueue(models.Model):
|
|||||||
"""
|
"""
|
||||||
Waiting queue for managing patient flow.
|
Waiting queue for managing patient flow.
|
||||||
"""
|
"""
|
||||||
QUEUE_TYPE_CHOICES=[
|
|
||||||
('PROVIDER', 'Provider Queue'),
|
class QueueType(models.TextChoices):
|
||||||
('SPECIALTY', 'Specialty Queue'),
|
PROVIDER = 'PROVIDER', 'Provider Queue'
|
||||||
('LOCATION', 'Location Queue'),
|
SPECIALTY = 'SPECIALTY', 'Specialty Queue'
|
||||||
('PROCEDURE', 'Procedure Queue'),
|
LOCATION = 'LOCATION', 'Location Queue'
|
||||||
('EMERGENCY', 'Emergency Queue'),
|
PROCEDURE = 'PROCEDURE', 'Procedure Queue'
|
||||||
]
|
EMERGENCY = 'EMERGENCY', 'Emergency Queue'
|
||||||
|
|
||||||
# Tenant relationship
|
# Tenant relationship
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
'core.Tenant',
|
'core.Tenant',
|
||||||
@ -624,7 +627,7 @@ class WaitingQueue(models.Model):
|
|||||||
# Queue Type and Configuration
|
# Queue Type and Configuration
|
||||||
queue_type = models.CharField(
|
queue_type = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=QUEUE_TYPE_CHOICES,
|
choices=QueueType.choices,
|
||||||
help_text='Type of queue'
|
help_text='Type of queue'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -726,14 +729,15 @@ class QueueEntry(models.Model):
|
|||||||
"""
|
"""
|
||||||
Individual entry in a waiting queue.
|
Individual entry in a waiting queue.
|
||||||
"""
|
"""
|
||||||
STATUS_CHOICES=[
|
|
||||||
('WAITING', 'Waiting'),
|
class QueueStatus(models.TextChoices):
|
||||||
('CALLED', 'Called'),
|
WAITING = 'WAITING', 'Waiting'
|
||||||
('IN_SERVICE', 'In Service'),
|
CALLED = 'CALLED', 'Called'
|
||||||
('COMPLETED', 'Completed'),
|
IN_SERVICE = 'IN_SERVICE', 'In Service'
|
||||||
('LEFT', 'Left Queue'),
|
COMPLETED = 'COMPLETED', 'Completed'
|
||||||
('NO_SHOW', 'No Show'),
|
LEFT = 'LEFT', 'Left Queue'
|
||||||
]
|
NO_SHOW = 'NO_SHOW', 'No Show'
|
||||||
|
|
||||||
# Queue and Patient
|
# Queue and Patient
|
||||||
queue = models.ForeignKey(
|
queue = models.ForeignKey(
|
||||||
WaitingQueue,
|
WaitingQueue,
|
||||||
@ -795,8 +799,8 @@ class QueueEntry(models.Model):
|
|||||||
# Status
|
# Status
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=STATUS_CHOICES,
|
choices=QueueStatus.choices,
|
||||||
default='WAITING',
|
default=QueueStatus.WAITING,
|
||||||
help_text='Queue entry status'
|
help_text='Queue entry status'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -878,22 +882,23 @@ class TelemedicineSession(models.Model):
|
|||||||
"""
|
"""
|
||||||
Telemedicine session management.
|
Telemedicine session management.
|
||||||
"""
|
"""
|
||||||
PLATFORM_CHOICES=[
|
|
||||||
('ZOOM', 'Zoom'),
|
class Platform(models.TextChoices):
|
||||||
('TEAMS', 'Microsoft Teams'),
|
ZOOM = 'ZOOM', 'Zoom'
|
||||||
('WEBEX', 'Cisco Webex'),
|
TEAMS = 'TEAMS', 'Microsoft Teams'
|
||||||
('DOXY', 'Doxy.me'),
|
WEBEX = 'WEBEX', 'Cisco Webex'
|
||||||
('CUSTOM', 'Custom Platform'),
|
DOXY = 'DOXY', 'Doxy.me'
|
||||||
('OTHER', 'Other'),
|
CUSTOM = 'CUSTOM', 'Custom Platform'
|
||||||
]
|
OTHER = 'OTHER', 'Other'
|
||||||
STATUS_CHOICES=[
|
|
||||||
('SCHEDULED', 'Scheduled'),
|
class SessionStatus(models.TextChoices):
|
||||||
('READY', 'Ready to Start'),
|
SCHEDULED = 'SCHEDULED', 'Scheduled'
|
||||||
('IN_PROGRESS', 'In Progress'),
|
READY = 'READY', 'Ready to Start'
|
||||||
('COMPLETED', 'Completed'),
|
IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
|
||||||
('CANCELLED', 'Cancelled'),
|
COMPLETED = 'COMPLETED', 'Completed'
|
||||||
('FAILED', 'Failed'),
|
CANCELLED = 'CANCELLED', 'Cancelled'
|
||||||
]
|
FAILED = 'FAILED', 'Failed'
|
||||||
|
|
||||||
# Session Information
|
# Session Information
|
||||||
session_id = models.UUIDField(
|
session_id = models.UUIDField(
|
||||||
default=uuid.uuid4,
|
default=uuid.uuid4,
|
||||||
@ -913,7 +918,7 @@ class TelemedicineSession(models.Model):
|
|||||||
# Platform Information
|
# Platform Information
|
||||||
platform = models.CharField(
|
platform = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=PLATFORM_CHOICES,
|
choices=Platform.choices,
|
||||||
help_text='Telemedicine platform'
|
help_text='Telemedicine platform'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -959,8 +964,8 @@ class TelemedicineSession(models.Model):
|
|||||||
# Session Status
|
# Session Status
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=STATUS_CHOICES,
|
choices=SessionStatus.choices,
|
||||||
default='SCHEDULED',
|
default=SessionStatus.SCHEDULED,
|
||||||
help_text='Session status'
|
help_text='Session status'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1182,85 +1187,85 @@ class WaitingList(models.Model):
|
|||||||
Patient waiting list for appointment scheduling.
|
Patient waiting list for appointment scheduling.
|
||||||
Follows healthcare industry standards for patient queue management.
|
Follows healthcare industry standards for patient queue management.
|
||||||
"""
|
"""
|
||||||
APPOINTMENT_TYPE_CHOICES = [
|
|
||||||
('CONSULTATION', 'Consultation'),
|
class AppointmentType(models.TextChoices):
|
||||||
('FOLLOW_UP', 'Follow-up'),
|
CONSULTATION = 'CONSULTATION', 'Consultation'
|
||||||
('PROCEDURE', 'Procedure'),
|
FOLLOW_UP = 'FOLLOW_UP', 'Follow-up'
|
||||||
('SURGERY', 'Surgery'),
|
PROCEDURE = 'PROCEDURE', 'Procedure'
|
||||||
('DIAGNOSTIC', 'Diagnostic'),
|
SURGERY = 'SURGERY', 'Surgery'
|
||||||
('THERAPY', 'Therapy'),
|
DIAGNOSTIC = 'DIAGNOSTIC', 'Diagnostic'
|
||||||
('VACCINATION', 'Vaccination'),
|
THERAPY = 'THERAPY', 'Therapy'
|
||||||
('SCREENING', 'Screening'),
|
VACCINATION = 'VACCINATION', 'Vaccination'
|
||||||
('EMERGENCY', 'Emergency'),
|
SCREENING = 'SCREENING', 'Screening'
|
||||||
('TELEMEDICINE', 'Telemedicine'),
|
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||||
('OTHER', 'Other'),
|
TELEMEDICINE = 'TELEMEDICINE', 'Telemedicine'
|
||||||
]
|
OTHER = 'OTHER', 'Other'
|
||||||
SPECIALTY_CHOICES = [
|
|
||||||
('FAMILY_MEDICINE', 'Family Medicine'),
|
class Specialty(models.TextChoices):
|
||||||
('INTERNAL_MEDICINE', 'Internal Medicine'),
|
FAMILY_MEDICINE = 'FAMILY_MEDICINE', 'Family Medicine'
|
||||||
('PEDIATRICS', 'Pediatrics'),
|
INTERNAL_MEDICINE = 'INTERNAL_MEDICINE', 'Internal Medicine'
|
||||||
('CARDIOLOGY', 'Cardiology'),
|
PEDIATRICS = 'PEDIATRICS', 'Pediatrics'
|
||||||
('DERMATOLOGY', 'Dermatology'),
|
CARDIOLOGY = 'CARDIOLOGY', 'Cardiology'
|
||||||
('ENDOCRINOLOGY', 'Endocrinology'),
|
DERMATOLOGY = 'DERMATOLOGY', 'Dermatology'
|
||||||
('GASTROENTEROLOGY', 'Gastroenterology'),
|
ENDOCRINOLOGY = 'ENDOCRINOLOGY', 'Endocrinology'
|
||||||
('NEUROLOGY', 'Neurology'),
|
GASTROENTEROLOGY = 'GASTROENTEROLOGY', 'Gastroenterology'
|
||||||
('ONCOLOGY', 'Oncology'),
|
NEUROLOGY = 'NEUROLOGY', 'Neurology'
|
||||||
('ORTHOPEDICS', 'Orthopedics'),
|
ONCOLOGY = 'ONCOLOGY', 'Oncology'
|
||||||
('PSYCHIATRY', 'Psychiatry'),
|
ORTHOPEDICS = 'ORTHOPEDICS', 'Orthopedics'
|
||||||
('RADIOLOGY', 'Radiology'),
|
PSYCHIATRY = 'PSYCHIATRY', 'Psychiatry'
|
||||||
('SURGERY', 'Surgery'),
|
RADIOLOGY = 'RADIOLOGY', 'Radiology'
|
||||||
('UROLOGY', 'Urology'),
|
SURGERY = 'SURGERY', 'Surgery'
|
||||||
('GYNECOLOGY', 'Gynecology'),
|
UROLOGY = 'UROLOGY', 'Urology'
|
||||||
('OPHTHALMOLOGY', 'Ophthalmology'),
|
GYNECOLOGY = 'GYNECOLOGY', 'Gynecology'
|
||||||
('ENT', 'Ear, Nose & Throat'),
|
OPHTHALMOLOGY = 'OPHTHALMOLOGY', 'Ophthalmology'
|
||||||
('EMERGENCY', 'Emergency Medicine'),
|
ENT = 'ENT', 'Ear, Nose & Throat'
|
||||||
('OTHER', 'Other'),
|
EMERGENCY = 'EMERGENCY', 'Emergency Medicine'
|
||||||
]
|
OTHER = 'OTHER', 'Other'
|
||||||
PRIORITY_CHOICES = [
|
|
||||||
('ROUTINE', 'Routine'),
|
class Priority(models.TextChoices):
|
||||||
('URGENT', 'Urgent'),
|
ROUTINE = 'ROUTINE', 'Routine'
|
||||||
('STAT', 'STAT'),
|
URGENT = 'URGENT', 'Urgent'
|
||||||
('EMERGENCY', 'Emergency'),
|
STAT = 'STAT', 'STAT'
|
||||||
]
|
EMERGENCY = 'EMERGENCY', 'Emergency'
|
||||||
CONTACT_METHOD_CHOICES = [
|
|
||||||
('PHONE', 'Phone'),
|
class ContactMethod(models.TextChoices):
|
||||||
('EMAIL', 'Email'),
|
PHONE = 'PHONE', 'Phone'
|
||||||
('SMS', 'SMS'),
|
EMAIL = 'EMAIL', 'Email'
|
||||||
('PORTAL', 'Patient Portal'),
|
SMS = 'SMS', 'SMS'
|
||||||
('MAIL', 'Mail'),
|
PORTAL = 'PORTAL', 'Patient Portal'
|
||||||
]
|
MAIL = 'MAIL', 'Mail'
|
||||||
STATUS_CHOICES = [
|
|
||||||
('ACTIVE', 'Active'),
|
class RequestStatus(models.TextChoices):
|
||||||
('CONTACTED', 'Contacted'),
|
ACTIVE = 'ACTIVE', 'Active'
|
||||||
('OFFERED', 'Appointment Offered'),
|
CONTACTED = 'CONTACTED', 'Contacted'
|
||||||
('SCHEDULED', 'Scheduled'),
|
OFFERED = 'OFFERED', 'Appointment Offered'
|
||||||
('CANCELLED', 'Cancelled'),
|
SCHEDULED = 'SCHEDULED', 'Scheduled'
|
||||||
('EXPIRED', 'Expired'),
|
CANCELLED = 'CANCELLED', 'Cancelled'
|
||||||
('TRANSFERRED', 'Transferred'),
|
EXPIRED = 'EXPIRED', 'Expired'
|
||||||
]
|
TRANSFERRED = 'TRANSFERRED', 'Transferred'
|
||||||
AUTHORIZATION_STATUS_CHOICES = [
|
|
||||||
('NOT_REQUIRED', 'Not Required'),
|
class AuthorizationStatus(models.TextChoices):
|
||||||
('PENDING', 'Pending'),
|
NOT_REQUIRED = 'NOT_REQUIRED', 'Not Required'
|
||||||
('APPROVED', 'Approved'),
|
PENDING = 'PENDING', 'Pending'
|
||||||
('DENIED', 'Denied'),
|
APPROVED = 'APPROVED', 'Approved'
|
||||||
('EXPIRED', 'Expired'),
|
DENIED = 'DENIED', 'Denied'
|
||||||
]
|
EXPIRED = 'EXPIRED', 'Expired'
|
||||||
REFERRAL_URGENCY_CHOICES = [
|
|
||||||
('ROUTINE', 'Routine'),
|
class ReferralUrgency(models.TextChoices):
|
||||||
('URGENT', 'Urgent'),
|
ROUTINE = 'ROUTINE', 'Routine'
|
||||||
('STAT', 'STAT'),
|
URGENT = 'URGENT', 'Urgent'
|
||||||
]
|
STAT = 'STAT', 'STAT'
|
||||||
REMOVAL_REASON_CHOICES = [
|
|
||||||
('SCHEDULED', 'Appointment Scheduled'),
|
class RemovalReason(models.TextChoices):
|
||||||
('PATIENT_CANCELLED', 'Patient Cancelled'),
|
SCHEDULED = 'SCHEDULED', 'Appointment Scheduled'
|
||||||
('PROVIDER_CANCELLED', 'Provider Cancelled'),
|
PATIENT_CANCELLED = 'PATIENT_CANCELLED', 'Patient Cancelled'
|
||||||
('NO_RESPONSE', 'No Response to Contact'),
|
PROVIDER_CANCELLED = 'PROVIDER_CANCELLED', 'Provider Cancelled'
|
||||||
('INSURANCE_ISSUE', 'Insurance Issue'),
|
NO_RESPONSE = 'NO_RESPONSE', 'No Response to Contact'
|
||||||
('TRANSFERRED', 'Transferred to Another Provider'),
|
INSURANCE_ISSUE = 'INSURANCE_ISSUE', 'Insurance Issue'
|
||||||
('EXPIRED', 'Entry Expired'),
|
TRANSFERRED = 'TRANSFERRED', 'Transferred to Another Provider'
|
||||||
('DUPLICATE', 'Duplicate Entry'),
|
EXPIRED = 'EXPIRED', 'Entry Expired'
|
||||||
('OTHER', 'Other'),
|
DUPLICATE = 'DUPLICATE', 'Duplicate Entry'
|
||||||
]
|
OTHER = 'OTHER', 'Other'
|
||||||
|
|
||||||
# Basic Identifiers
|
# Basic Identifiers
|
||||||
waiting_list_id = models.UUIDField(
|
waiting_list_id = models.UUIDField(
|
||||||
@ -1305,21 +1310,21 @@ class WaitingList(models.Model):
|
|||||||
|
|
||||||
appointment_type = models.CharField(
|
appointment_type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=APPOINTMENT_TYPE_CHOICES,
|
choices=AppointmentType.choices,
|
||||||
help_text='Type of appointment requested'
|
help_text='Type of appointment requested'
|
||||||
)
|
)
|
||||||
|
|
||||||
specialty = models.CharField(
|
specialty = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
choices=SPECIALTY_CHOICES,
|
choices=Specialty.choices,
|
||||||
help_text='Medical specialty required'
|
help_text='Medical specialty required'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Priority and Clinical Information
|
# Priority and Clinical Information
|
||||||
priority = models.CharField(
|
priority = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=PRIORITY_CHOICES,
|
choices=Priority.choices,
|
||||||
default='ROUTINE',
|
default=Priority.ROUTINE,
|
||||||
help_text='Clinical priority level'
|
help_text='Clinical priority level'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1385,8 +1390,8 @@ class WaitingList(models.Model):
|
|||||||
# Communication Preferences
|
# Communication Preferences
|
||||||
contact_method = models.CharField(
|
contact_method = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=CONTACT_METHOD_CHOICES,
|
choices=ContactMethod.choices,
|
||||||
default='PHONE',
|
default=ContactMethod.PHONE,
|
||||||
help_text='Preferred contact method'
|
help_text='Preferred contact method'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1406,8 +1411,8 @@ class WaitingList(models.Model):
|
|||||||
# Status and Workflow
|
# Status and Workflow
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=STATUS_CHOICES,
|
choices=RequestStatus.choices,
|
||||||
default='ACTIVE',
|
default=RequestStatus.ACTIVE,
|
||||||
help_text='Waiting list status'
|
help_text='Waiting list status'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1495,8 +1500,8 @@ class WaitingList(models.Model):
|
|||||||
|
|
||||||
authorization_status = models.CharField(
|
authorization_status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=AUTHORIZATION_STATUS_CHOICES,
|
choices=AuthorizationStatus.choices,
|
||||||
default='NOT_REQUIRED',
|
default=AuthorizationStatus.NOT_REQUIRED,
|
||||||
help_text='Authorization status'
|
help_text='Authorization status'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1523,8 +1528,8 @@ class WaitingList(models.Model):
|
|||||||
|
|
||||||
referral_urgency = models.CharField(
|
referral_urgency = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=REFERRAL_URGENCY_CHOICES,
|
choices=ReferralUrgency.choices,
|
||||||
default='ROUTINE',
|
default=ReferralUrgency.ROUTINE,
|
||||||
help_text='Referral urgency level'
|
help_text='Referral urgency level'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1540,7 +1545,7 @@ class WaitingList(models.Model):
|
|||||||
|
|
||||||
removal_reason = models.CharField(
|
removal_reason = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=REMOVAL_REASON_CHOICES,
|
choices=RemovalReason.choices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Reason for removal from waiting list'
|
help_text='Reason for removal from waiting list'
|
||||||
@ -1701,35 +1706,35 @@ class WaitingListContactLog(models.Model):
|
|||||||
Contact log for waiting list entries.
|
Contact log for waiting list entries.
|
||||||
Tracks all communication attempts with patients on waiting list.
|
Tracks all communication attempts with patients on waiting list.
|
||||||
"""
|
"""
|
||||||
CONTACT_METHOD_CHOICES = [
|
|
||||||
('PHONE', 'Phone Call'),
|
class ContactMethod(models.TextChoices):
|
||||||
('EMAIL', 'Email'),
|
PHONE = 'PHONE', 'Phone Call'
|
||||||
('SMS', 'SMS'),
|
EMAIL = 'EMAIL', 'Email'
|
||||||
('PORTAL', 'Patient Portal Message'),
|
SMS = 'SMS', 'SMS'
|
||||||
('MAIL', 'Mail'),
|
PORTAL = 'PORTAL', 'Patient Portal Message'
|
||||||
('IN_PERSON', 'In Person'),
|
MAIL = 'MAIL', 'Mail'
|
||||||
]
|
IN_PERSON = 'IN_PERSON', 'In Person'
|
||||||
CONTACT_OUTCOME_CHOICES = [
|
|
||||||
('SUCCESSFUL', 'Successful Contact'),
|
class ContactOutcome(models.TextChoices):
|
||||||
('NO_ANSWER', 'No Answer'),
|
SUCCESSFUL = 'SUCCESSFUL', 'Successful Contact'
|
||||||
('BUSY', 'Line Busy'),
|
NO_ANSWER = 'NO_ANSWER', 'No Answer'
|
||||||
('VOICEMAIL', 'Left Voicemail'),
|
BUSY = 'BUSY', 'Line Busy'
|
||||||
('EMAIL_SENT', 'Email Sent'),
|
VOICEMAIL = 'VOICEMAIL', 'Left Voicemail'
|
||||||
('EMAIL_BOUNCED', 'Email Bounced'),
|
EMAIL_SENT = 'EMAIL_SENT', 'Email Sent'
|
||||||
('SMS_SENT', 'SMS Sent'),
|
EMAIL_BOUNCED = 'EMAIL_BOUNCED', 'Email Bounced'
|
||||||
('SMS_FAILED', 'SMS Failed'),
|
SMS_SENT = 'SMS_SENT', 'SMS Sent'
|
||||||
('WRONG_NUMBER', 'Wrong Number'),
|
SMS_FAILED = 'SMS_FAILED', 'SMS Failed'
|
||||||
('DECLINED', 'Patient Declined'),
|
WRONG_NUMBER = 'WRONG_NUMBER', 'Wrong Number'
|
||||||
]
|
DECLINED = 'DECLINED', 'Patient Declined'
|
||||||
PATIENT_RESPONSE_CHOICES = [
|
|
||||||
('ACCEPTED', 'Accepted Appointment'),
|
class PatientResponse(models.TextChoices):
|
||||||
('DECLINED', 'Declined Appointment'),
|
ACCEPTED = 'ACCEPTED', 'Accepted Appointment'
|
||||||
('REQUESTED_DIFFERENT', 'Requested Different Time'),
|
DECLINED = 'DECLINED', 'Declined Appointment'
|
||||||
('WILL_CALL_BACK', 'Will Call Back'),
|
REQUESTED_DIFFERENT = 'REQUESTED_DIFFERENT', 'Requested Different Time'
|
||||||
('NO_LONGER_NEEDED', 'No Longer Needed'),
|
WILL_CALL_BACK = 'WILL_CALL_BACK', 'Will Call Back'
|
||||||
('INSURANCE_ISSUE', 'Insurance Issue'),
|
NO_LONGER_NEEDED = 'NO_LONGER_NEEDED', 'No Longer Needed'
|
||||||
('NO_RESPONSE', 'No Response'),
|
INSURANCE_ISSUE = 'INSURANCE_ISSUE', 'Insurance Issue'
|
||||||
]
|
NO_RESPONSE = 'NO_RESPONSE', 'No Response'
|
||||||
|
|
||||||
waiting_list_entry = models.ForeignKey(
|
waiting_list_entry = models.ForeignKey(
|
||||||
WaitingList,
|
WaitingList,
|
||||||
@ -1745,13 +1750,13 @@ class WaitingListContactLog(models.Model):
|
|||||||
|
|
||||||
contact_method = models.CharField(
|
contact_method = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=CONTACT_METHOD_CHOICES,
|
choices=ContactMethod.choices,
|
||||||
help_text='Method of contact used'
|
help_text='Method of contact used'
|
||||||
)
|
)
|
||||||
|
|
||||||
contact_outcome = models.CharField(
|
contact_outcome = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=CONTACT_OUTCOME_CHOICES,
|
choices=ContactOutcome.choices,
|
||||||
help_text='Outcome of contact attempt'
|
help_text='Outcome of contact attempt'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1774,7 +1779,7 @@ class WaitingListContactLog(models.Model):
|
|||||||
|
|
||||||
patient_response = models.CharField(
|
patient_response = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=PATIENT_RESPONSE_CHOICES,
|
choices=PatientResponse.choices,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Patient response to contact'
|
help_text='Patient response to contact'
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
{% block title %}Slot Availability - Appointment Management{% endblock %}
|
{% block title %}Slot Availability - Appointment Management{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block css %}
|
||||||
<link href="{% static 'assets/plugins/fullcalendar/main.min.css' %}" rel="stylesheet" />
|
<link href="{% static 'plugins/@fullcalendar/main.min.css' %}" rel="stylesheet" />
|
||||||
<link href="{% static 'assets/plugins/select2/css/select2.min.css' %}" rel="stylesheet" />
|
<link href="{% static 'plugins/select2/css/select2.min.css' %}" rel="stylesheet" />
|
||||||
<style>
|
<style>
|
||||||
.availability-header {
|
.availability-header {
|
||||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||||
|
|||||||
@ -18,7 +18,7 @@ from core.models import Tenant
|
|||||||
from hr.models import Employee, Department
|
from hr.models import Employee, Department
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
User = get_user_model()
|
from accounts.models import User
|
||||||
|
|
||||||
# Saudi-specific healthcare data
|
# Saudi-specific healthcare data
|
||||||
SAUDI_SPECIALTIES = [
|
SAUDI_SPECIALTIES = [
|
||||||
@ -134,8 +134,8 @@ def get_providers_for_tenant(tenants):
|
|||||||
if not providers:
|
if not providers:
|
||||||
for tenant in tenants:
|
for tenant in tenants:
|
||||||
tenant_providers = User.objects.filter(
|
tenant_providers = User.objects.filter(
|
||||||
employee_profile__tenant=tenant,
|
tenant=tenant,
|
||||||
role__in=['PHYSICIAN', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT', 'RADIOLOGIST', 'SURGEON']
|
employee_profile__role__in=['PHYSICIAN', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT', 'RADIOLOGIST', 'SURGEON']
|
||||||
).select_related('employee_profile')
|
).select_related('employee_profile')
|
||||||
providers.extend(list(tenant_providers))
|
providers.extend(list(tenant_providers))
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ def get_providers_for_tenant(tenants):
|
|||||||
tenant_providers = User.objects.filter(
|
tenant_providers = User.objects.filter(
|
||||||
tenant=tenant,
|
tenant=tenant,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
role__icontains='PHYSICIAN'
|
employee_profile__role__icontains='PHYSICIAN'
|
||||||
)
|
)
|
||||||
providers.extend(list(tenant_providers))
|
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 billing.utils
|
||||||
import django.db.models.deletion
|
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
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -12,8 +12,6 @@ class Migration(migrations.Migration):
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
("billing", "0001_initial"),
|
("billing", "0001_initial"),
|
||||||
("core", "0001_initial"),
|
("core", "0001_initial"),
|
||||||
("emr", "0001_initial"),
|
|
||||||
("inpatients", "0001_initial"),
|
|
||||||
("patients", "0001_initial"),
|
("patients", "0001_initial"),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
@ -109,310 +107,4 @@ class Migration(migrations.Migration):
|
|||||||
to="billing.insuranceclaim",
|
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 patients.models import PatientProfile, InsuranceInfo
|
||||||
from emr.models import Encounter
|
from emr.models import Encounter
|
||||||
from inpatients.models import Admission
|
from inpatients.models import Admission
|
||||||
from billing.models import (
|
from billing.models import *
|
||||||
MedicalBill, BillLineItem, InsuranceClaim, Payment,
|
from accounts.models import User
|
||||||
ClaimStatusUpdate, BillingConfiguration
|
|
||||||
)
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
from django.db import IntegrityError, transaction
|
from django.db import IntegrityError, transaction
|
||||||
from django.db.models import DecimalField, CharField, IntegerField
|
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.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
@ -31,12 +31,12 @@ class Migration(migrations.Migration):
|
|||||||
"name",
|
"name",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("whole_blood", "Whole Blood"),
|
("WHOLE_BLOOD", "Whole Blood"),
|
||||||
("packed_rbc", "Packed Red Blood Cells"),
|
("PACKED_RBC", "Packed Red Blood Cells"),
|
||||||
("fresh_frozen_plasma", "Fresh Frozen Plasma"),
|
("FRESH_FROZEN_PLASMA", "Fresh Frozen Plasma"),
|
||||||
("platelets", "Platelets"),
|
("PLATELETS", "Platelets"),
|
||||||
("cryoprecipitate", "Cryoprecipitate"),
|
("CRYOPRECIPITATE", "Cryoprecipitate"),
|
||||||
("granulocytes", "Granulocytes"),
|
("GRANULOCYTES", "Granulocytes"),
|
||||||
],
|
],
|
||||||
max_length=50,
|
max_length=50,
|
||||||
unique=True,
|
unique=True,
|
||||||
@ -68,15 +68,15 @@ class Migration(migrations.Migration):
|
|||||||
"test_type",
|
"test_type",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("abo_rh", "ABO/Rh Typing"),
|
("ABO_RH", "ABO/Rh Typing"),
|
||||||
("antibody_screen", "Antibody Screening"),
|
("ANTIBODY_SCREEN", "Antibody Screening"),
|
||||||
("hiv", "HIV"),
|
("HIV", "HIV"),
|
||||||
("hbv", "Hepatitis B"),
|
("HBV", "Hepatitis B"),
|
||||||
("hcv", "Hepatitis C"),
|
("HCV", "Hepatitis C"),
|
||||||
("syphilis", "Syphilis"),
|
("SYPHILIS", "Syphilis"),
|
||||||
("htlv", "HTLV"),
|
("HTLV", "HTLV"),
|
||||||
("cmv", "CMV"),
|
("CMV", "CMV"),
|
||||||
("malaria", "Malaria"),
|
("MALARIA", "Malaria"),
|
||||||
],
|
],
|
||||||
max_length=20,
|
max_length=20,
|
||||||
),
|
),
|
||||||
@ -85,12 +85,12 @@ class Migration(migrations.Migration):
|
|||||||
"result",
|
"result",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("positive", "Positive"),
|
("POSITIVE", "Positive"),
|
||||||
("negative", "Negative"),
|
("NEGATIVE", "Negative"),
|
||||||
("indeterminate", "Indeterminate"),
|
("INDETERMINATE", "Indeterminate"),
|
||||||
("pending", "Pending"),
|
("PENDING", "Pending"),
|
||||||
],
|
],
|
||||||
default="pending",
|
default="PENDING",
|
||||||
max_length=15,
|
max_length=15,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -124,17 +124,17 @@ class Migration(migrations.Migration):
|
|||||||
"status",
|
"status",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("collected", "Collected"),
|
("COLLECTED", "Collected"),
|
||||||
("testing", "Testing"),
|
("TESTING", "Testing"),
|
||||||
("quarantine", "Quarantine"),
|
("QUARANTINE", "Quarantine"),
|
||||||
("available", "Available"),
|
("AVAILABLE", "Available"),
|
||||||
("reserved", "Reserved"),
|
("RESERVED", "Reserved"),
|
||||||
("issued", "Issued"),
|
("ISSUED", "Issued"),
|
||||||
("transfused", "Transfused"),
|
("TRANSFUSED", "Transfused"),
|
||||||
("expired", "Expired"),
|
("EXPIRED", "Expired"),
|
||||||
("discarded", "Discarded"),
|
("DISCARDED", "Discarded"),
|
||||||
],
|
],
|
||||||
default="collected",
|
default="COLLECTED",
|
||||||
max_length=20,
|
max_length=20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -166,10 +166,10 @@ class Migration(migrations.Migration):
|
|||||||
"test_type",
|
"test_type",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("major", "Major Crossmatch"),
|
("MAJOR", "Major Crossmatch"),
|
||||||
("minor", "Minor Crossmatch"),
|
("MINOR", "Minor Crossmatch"),
|
||||||
("immediate_spin", "Immediate Spin"),
|
("IMMEDIATE_SPIN", "Immediate Spin"),
|
||||||
("antiglobulin", "Antiglobulin Test"),
|
("ANTIGLOBULIN", "Antiglobulin Test"),
|
||||||
],
|
],
|
||||||
max_length=20,
|
max_length=20,
|
||||||
),
|
),
|
||||||
@ -178,11 +178,11 @@ class Migration(migrations.Migration):
|
|||||||
"compatibility",
|
"compatibility",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("compatible", "Compatible"),
|
("COMPATIBLE", "Compatible"),
|
||||||
("incompatible", "Incompatible"),
|
("INCOMPATIBLE", "Incompatible"),
|
||||||
("pending", "Pending"),
|
("PENDING", "Pending"),
|
||||||
],
|
],
|
||||||
default="pending",
|
default="PENDING",
|
||||||
max_length=15,
|
max_length=15,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -215,7 +215,11 @@ class Migration(migrations.Migration):
|
|||||||
(
|
(
|
||||||
"gender",
|
"gender",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[("M", "Male"), ("F", "Female"), ("O", "Other")],
|
choices=[
|
||||||
|
("MALE", "Male"),
|
||||||
|
("FEMALE", "Female"),
|
||||||
|
("OTHER", "Other"),
|
||||||
|
],
|
||||||
max_length=10,
|
max_length=10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -229,12 +233,12 @@ class Migration(migrations.Migration):
|
|||||||
"donor_type",
|
"donor_type",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("voluntary", "Voluntary"),
|
("VOLUNTARY", "Voluntary"),
|
||||||
("replacement", "Replacement"),
|
("REPLACEMENT", "Replacement"),
|
||||||
("autologous", "Autologous"),
|
("AUTOLOGOUS", "Autologous"),
|
||||||
("directed", "Directed"),
|
("DIRECTED", "Directed"),
|
||||||
],
|
],
|
||||||
default="voluntary",
|
default="VOLUNTARY",
|
||||||
max_length=20,
|
max_length=20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -242,12 +246,12 @@ class Migration(migrations.Migration):
|
|||||||
"status",
|
"status",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("active", "Active"),
|
("ACTIVE", "Active"),
|
||||||
("deferred", "Deferred"),
|
("DEFERRED", "Deferred"),
|
||||||
("permanently_deferred", "Permanently Deferred"),
|
("PERMANENTLY_DEFERRED", "Permanently Deferred"),
|
||||||
("inactive", "Inactive"),
|
("INACTIVE", "Inactive"),
|
||||||
],
|
],
|
||||||
default="active",
|
default="ACTIVE",
|
||||||
max_length=20,
|
max_length=20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -291,11 +295,11 @@ class Migration(migrations.Migration):
|
|||||||
"location_type",
|
"location_type",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("refrigerator", "Refrigerator"),
|
("REFRIGERATOR", "Refrigerator"),
|
||||||
("freezer", "Freezer"),
|
("FREEZER", "Freezer"),
|
||||||
("platelet_agitator", "Platelet Agitator"),
|
("PLATELET_AGITATOR", "Platelet Agitator"),
|
||||||
("quarantine", "Quarantine"),
|
("QUARANTINE", "Quarantine"),
|
||||||
("testing", "Testing Area"),
|
("TESTING", "Testing Area"),
|
||||||
],
|
],
|
||||||
max_length=20,
|
max_length=20,
|
||||||
),
|
),
|
||||||
@ -327,11 +331,11 @@ class Migration(migrations.Migration):
|
|||||||
"test_type",
|
"test_type",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("temperature_monitoring", "Temperature Monitoring"),
|
("TEMPERATURE_MONITORING", "Temperature Monitoring"),
|
||||||
("equipment_calibration", "Equipment Calibration"),
|
("EQUIPMENT_CALIBRATION", "Equipment Calibration"),
|
||||||
("reagent_testing", "Reagent Testing"),
|
("REAGENT_TESTING", "Reagent Testing"),
|
||||||
("proficiency_testing", "Proficiency Testing"),
|
("PROFICIENCY_TESTING", "Proficiency Testing"),
|
||||||
("process_validation", "Process Validation"),
|
("PROCESS_VALIDATION", "Process Validation"),
|
||||||
],
|
],
|
||||||
max_length=30,
|
max_length=30,
|
||||||
),
|
),
|
||||||
@ -345,9 +349,9 @@ class Migration(migrations.Migration):
|
|||||||
"status",
|
"status",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("pass", "Pass"),
|
("PASS", "Pass"),
|
||||||
("fail", "Fail"),
|
("FAIL", "Fail"),
|
||||||
("pending", "Pending"),
|
("PENDING", "Pending"),
|
||||||
],
|
],
|
||||||
max_length=10,
|
max_length=10,
|
||||||
),
|
),
|
||||||
@ -363,9 +367,9 @@ class Migration(migrations.Migration):
|
|||||||
models.CharField(
|
models.CharField(
|
||||||
blank=True,
|
blank=True,
|
||||||
choices=[
|
choices=[
|
||||||
("low", "Low"),
|
("LOW", "Low"),
|
||||||
("medium", "Medium"),
|
("MEDIUM", "Medium"),
|
||||||
("high", "High"),
|
("HIGH", "High"),
|
||||||
],
|
],
|
||||||
max_length=10,
|
max_length=10,
|
||||||
),
|
),
|
||||||
@ -377,9 +381,9 @@ class Migration(migrations.Migration):
|
|||||||
models.CharField(
|
models.CharField(
|
||||||
blank=True,
|
blank=True,
|
||||||
choices=[
|
choices=[
|
||||||
("open", "Open"),
|
("OPEN", "Open"),
|
||||||
("in_progress", "In Progress"),
|
("IN_PROGRESS", "In Progress"),
|
||||||
("closed", "Closed"),
|
("CLOSED", "Closed"),
|
||||||
],
|
],
|
||||||
max_length=20,
|
max_length=20,
|
||||||
),
|
),
|
||||||
@ -407,13 +411,13 @@ class Migration(migrations.Migration):
|
|||||||
"status",
|
"status",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("started", "Started"),
|
("STARTED", "Started"),
|
||||||
("in_progress", "In Progress"),
|
("IN_PROGRESS", "In Progress"),
|
||||||
("completed", "Completed"),
|
("COMPLETED", "Completed"),
|
||||||
("stopped", "Stopped"),
|
("STOPPED", "Stopped"),
|
||||||
("adverse_reaction", "Adverse Reaction"),
|
("ADVERSE_REACTION", "Adverse Reaction"),
|
||||||
],
|
],
|
||||||
default="started",
|
default="STARTED",
|
||||||
max_length=20,
|
max_length=20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -463,15 +467,15 @@ class Migration(migrations.Migration):
|
|||||||
"reaction_type",
|
"reaction_type",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("febrile", "Febrile Non-Hemolytic"),
|
("FEBRILE", "Febrile Non-Hemolytic"),
|
||||||
("allergic", "Allergic"),
|
("ALLERGIC", "Allergic"),
|
||||||
("hemolytic_acute", "Acute Hemolytic"),
|
("HEMOLYTIC_ACUTE", "Acute Hemolytic"),
|
||||||
("hemolytic_delayed", "Delayed Hemolytic"),
|
("HEMOLYTIC_DELAYED", "Delayed Hemolytic"),
|
||||||
("anaphylactic", "Anaphylactic"),
|
("ANAPHYLACTIC", "Anaphylactic"),
|
||||||
("septic", "Septic"),
|
("SEPTIC", "Septic"),
|
||||||
("circulatory_overload", "Circulatory Overload"),
|
("CIRCULATORY_OVERLOAD", "Circulatory Overload"),
|
||||||
("lung_injury", "Transfusion-Related Acute Lung Injury"),
|
("LUNG_INJURY", "Transfusion-Related Acute Lung Injury"),
|
||||||
("other", "Other"),
|
("OTHER", "Other"),
|
||||||
],
|
],
|
||||||
max_length=30,
|
max_length=30,
|
||||||
),
|
),
|
||||||
@ -480,10 +484,10 @@ class Migration(migrations.Migration):
|
|||||||
"severity",
|
"severity",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("mild", "Mild"),
|
("MILD", "Mild"),
|
||||||
("moderate", "Moderate"),
|
("MODERATE", "Moderate"),
|
||||||
("severe", "Severe"),
|
("SEVERE", "Severe"),
|
||||||
("life_threatening", "Life Threatening"),
|
("LIFE_THREATENING", "Life Threatening"),
|
||||||
],
|
],
|
||||||
max_length=20,
|
max_length=20,
|
||||||
),
|
),
|
||||||
@ -611,11 +615,11 @@ class Migration(migrations.Migration):
|
|||||||
"urgency",
|
"urgency",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("routine", "Routine"),
|
("ROUTINE", "Routine"),
|
||||||
("urgent", "Urgent"),
|
("URGENT", "Urgent"),
|
||||||
("emergency", "Emergency"),
|
("EMERGENCY", "Emergency"),
|
||||||
],
|
],
|
||||||
default="routine",
|
default="ROUTINE",
|
||||||
max_length=10,
|
max_length=10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -627,14 +631,14 @@ class Migration(migrations.Migration):
|
|||||||
"status",
|
"status",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
choices=[
|
choices=[
|
||||||
("pending", "Pending"),
|
("PENDING", "Pending"),
|
||||||
("processing", "Processing"),
|
("PROCESSING", "Processing"),
|
||||||
("ready", "Ready"),
|
("READY", "Ready"),
|
||||||
("issued", "Issued"),
|
("ISSUED", "Issued"),
|
||||||
("completed", "Completed"),
|
("COMPLETED", "Completed"),
|
||||||
("cancelled", "Cancelled"),
|
("CANCELLED", "Cancelled"),
|
||||||
],
|
],
|
||||||
default="pending",
|
default="PENDING",
|
||||||
max_length=15,
|
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
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
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.db import models
|
||||||
# from django.contrib.auth.models import User
|
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from hr.models import Department
|
from hr.models import Department
|
||||||
from patients.models import PatientProfile
|
from patients.models import PatientProfile
|
||||||
from accounts.models import User
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class BloodGroup(models.Model):
|
class BloodGroup(models.Model):
|
||||||
"""Blood group types (A, B, AB, O) with Rh factor"""
|
"""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
|
weight = models.FloatField(validators=[MinValueValidator(45.0)]) # Minimum weight for donation
|
||||||
height = models.FloatField(validators=[MinValueValidator(140.0)]) # In cm
|
height = models.FloatField(validators=[MinValueValidator(140.0)]) # In cm
|
||||||
notes = models.TextField(blank=True)
|
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)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ class BloodUnit(models.Model):
|
|||||||
bag_type = models.CharField(max_length=50)
|
bag_type = models.CharField(max_length=50)
|
||||||
anticoagulant = models.CharField(max_length=50, default='CPDA-1')
|
anticoagulant = models.CharField(max_length=50, default='CPDA-1')
|
||||||
collection_site = models.CharField(max_length=100)
|
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)
|
notes = models.TextField(blank=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=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)
|
test_type = models.CharField(max_length=20, choices=TestType.choices)
|
||||||
result = models.CharField(max_length=15, choices=TestResult.choices, default=TestResult.PENDING)
|
result = models.CharField(max_length=15, choices=TestResult.choices, default=TestResult.PENDING)
|
||||||
test_date = models.DateTimeField()
|
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)
|
equipment_used = models.CharField(max_length=100, blank=True)
|
||||||
lot_number = models.CharField(max_length=50, blank=True)
|
lot_number = models.CharField(max_length=50, blank=True)
|
||||||
notes = models.TextField(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)
|
blank=True)
|
||||||
verified_at = models.DateTimeField(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)
|
test_type = models.CharField(max_length=20, choices=CrossmatchTestType.choices)
|
||||||
compatibility = models.CharField(max_length=15, choices=CompatibilityStatus.choices, default=CompatibilityStatus.PENDING)
|
compatibility = models.CharField(max_length=15, choices=CompatibilityStatus.choices, default=CompatibilityStatus.PENDING)
|
||||||
test_date = models.DateTimeField()
|
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')
|
temperature = models.CharField(max_length=20, default='37°C')
|
||||||
incubation_time = models.PositiveIntegerField(default=15) # minutes
|
incubation_time = models.PositiveIntegerField(default=15) # minutes
|
||||||
notes = models.TextField(blank=True)
|
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)
|
blank=True)
|
||||||
verified_at = models.DateTimeField(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)
|
request_number = models.CharField(max_length=20, unique=True)
|
||||||
patient = models.ForeignKey(PatientProfile, on_delete=models.PROTECT, related_name='blood_requests')
|
patient = models.ForeignKey(PatientProfile, on_delete=models.PROTECT, related_name='blood_requests')
|
||||||
requesting_department = models.ForeignKey(Department, on_delete=models.PROTECT)
|
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)
|
component_requested = models.ForeignKey(BloodComponent, on_delete=models.PROTECT)
|
||||||
units_requested = models.PositiveIntegerField(validators=[MinValueValidator(1)])
|
units_requested = models.PositiveIntegerField(validators=[MinValueValidator(1)])
|
||||||
urgency = models.CharField(max_length=10, choices=UrgencyLevel.choices, default=UrgencyLevel.ROUTINE)
|
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)
|
status = models.CharField(max_length=15, choices=RequestStatus.choices, default=RequestStatus.PENDING)
|
||||||
request_date = models.DateTimeField(auto_now_add=True)
|
request_date = models.DateTimeField(auto_now_add=True)
|
||||||
required_by = models.DateTimeField()
|
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)
|
blank=True)
|
||||||
processed_at = models.DateTimeField(null=True, blank=True)
|
processed_at = models.DateTimeField(null=True, blank=True)
|
||||||
notes = models.TextField(blank=True)
|
notes = models.TextField(blank=True)
|
||||||
|
|
||||||
# Cancellation fields
|
# Cancellation fields
|
||||||
cancellation_reason = models.TextField(blank=True)
|
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)
|
blank=True)
|
||||||
cancellation_date = models.DateTimeField(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_request = models.ForeignKey(BloodRequest, on_delete=models.PROTECT, related_name='issues')
|
||||||
blood_unit = models.OneToOneField(BloodUnit, on_delete=models.PROTECT, related_name='issue')
|
blood_unit = models.OneToOneField(BloodUnit, on_delete=models.PROTECT, related_name='issue')
|
||||||
crossmatch = models.ForeignKey(CrossMatch, on_delete=models.PROTECT, null=True, blank=True)
|
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_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='issued_units')
|
||||||
issued_to = models.ForeignKey(User, on_delete=models.PROTECT, related_name='received_units') # Nurse/physician
|
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)
|
issue_date = models.DateTimeField(auto_now_add=True)
|
||||||
expiry_time = models.DateTimeField() # 4 hours from issue for RBC
|
expiry_time = models.DateTimeField() # 4 hours from issue for RBC
|
||||||
returned = models.BooleanField(default=False)
|
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)
|
status = models.CharField(max_length=20, choices=TransfusionStatus.choices, default=TransfusionStatus.STARTED)
|
||||||
volume_transfused = models.PositiveIntegerField(null=True, blank=True) # ml
|
volume_transfused = models.PositiveIntegerField(null=True, blank=True) # ml
|
||||||
transfusion_rate = models.CharField(max_length=50, blank=True) # ml/hour
|
transfusion_rate = models.CharField(max_length=50, blank=True) # ml/hour
|
||||||
administered_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='administered_transfusions')
|
administered_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='administered_transfusions')
|
||||||
witnessed_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='witnessed_transfusions', null=True,
|
witnessed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='witnessed_transfusions', null=True,
|
||||||
blank=True)
|
blank=True)
|
||||||
pre_transfusion_vitals = models.JSONField(default=dict) # BP, HR, Temp, etc.
|
pre_transfusion_vitals = models.JSONField(default=dict) # BP, HR, Temp, etc.
|
||||||
post_transfusion_vitals = models.JSONField(default=dict)
|
post_transfusion_vitals = models.JSONField(default=dict)
|
||||||
@ -374,9 +374,9 @@ class Transfusion(models.Model):
|
|||||||
|
|
||||||
# Completion/Stop fields
|
# Completion/Stop fields
|
||||||
stop_reason = models.TextField(blank=True)
|
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)
|
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)
|
blank=True)
|
||||||
completion_notes = models.TextField(blank=True)
|
completion_notes = models.TextField(blank=True)
|
||||||
|
|
||||||
@ -420,8 +420,8 @@ class AdverseReaction(models.Model):
|
|||||||
symptoms = models.TextField()
|
symptoms = models.TextField()
|
||||||
treatment_given = models.TextField()
|
treatment_given = models.TextField()
|
||||||
outcome = models.TextField()
|
outcome = models.TextField()
|
||||||
reported_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='reported_reactions')
|
reported_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='reported_reactions')
|
||||||
investigated_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='investigated_reactions',
|
investigated_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='investigated_reactions',
|
||||||
null=True, blank=True)
|
null=True, blank=True)
|
||||||
investigation_notes = models.TextField(blank=True)
|
investigation_notes = models.TextField(blank=True)
|
||||||
regulatory_reported = models.BooleanField(default=False)
|
regulatory_reported = models.BooleanField(default=False)
|
||||||
@ -488,8 +488,8 @@ class QualityControl(models.Model):
|
|||||||
expected_results = models.TextField()
|
expected_results = models.TextField()
|
||||||
actual_results = models.TextField()
|
actual_results = models.TextField()
|
||||||
status = models.CharField(max_length=10, choices=QualityTestStatus.choices)
|
status = models.CharField(max_length=10, choices=QualityTestStatus.choices)
|
||||||
performed_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='qc_tests')
|
performed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='qc_tests')
|
||||||
reviewed_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='reviewed_qc_tests', null=True,
|
reviewed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='reviewed_qc_tests', null=True,
|
||||||
blank=True)
|
blank=True)
|
||||||
review_date = models.DateTimeField(null=True, blank=True)
|
review_date = models.DateTimeField(null=True, blank=True)
|
||||||
review_notes = models.TextField(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_number = models.CharField(max_length=50, blank=True)
|
||||||
capa_priority = models.CharField(max_length=10, choices=[('LOW', 'Low'), ('MEDIUM', 'Medium'), ('HIGH', 'High')],
|
capa_priority = models.CharField(max_length=10, choices=[('LOW', 'Low'), ('MEDIUM', 'Medium'), ('HIGH', 'High')],
|
||||||
blank=True)
|
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)
|
blank=True)
|
||||||
capa_date = models.DateTimeField(null=True, blank=True)
|
capa_date = models.DateTimeField(null=True, blank=True)
|
||||||
capa_assessment = models.TextField(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 django.db.models.deletion
|
||||||
import uuid
|
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
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -6,10 +6,10 @@ import uuid
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from accounts.models import User
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class Message(models.Model):
|
class Message(models.Model):
|
||||||
@ -83,7 +83,7 @@ class Message(models.Model):
|
|||||||
|
|
||||||
# Sender and recipients
|
# Sender and recipients
|
||||||
sender = models.ForeignKey(
|
sender = models.ForeignKey(
|
||||||
User,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='sent_messages',
|
related_name='sent_messages',
|
||||||
help_text="Message sender"
|
help_text="Message sender"
|
||||||
@ -249,7 +249,7 @@ class MessageRecipient(models.Model):
|
|||||||
help_text="Type of recipient"
|
help_text="Type of recipient"
|
||||||
)
|
)
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -472,7 +472,7 @@ class NotificationTemplate(models.Model):
|
|||||||
help_text="Last update timestamp"
|
help_text="Last update timestamp"
|
||||||
)
|
)
|
||||||
created_by = models.ForeignKey(
|
created_by = models.ForeignKey(
|
||||||
User,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -584,7 +584,7 @@ class AlertRule(models.Model):
|
|||||||
|
|
||||||
# Recipients
|
# Recipients
|
||||||
default_recipients = models.ManyToManyField(
|
default_recipients = models.ManyToManyField(
|
||||||
User,
|
settings.AUTH_USER_MODEL,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Default alert recipients"
|
help_text="Default alert recipients"
|
||||||
)
|
)
|
||||||
@ -629,7 +629,7 @@ class AlertRule(models.Model):
|
|||||||
help_text="Last update timestamp"
|
help_text="Last update timestamp"
|
||||||
)
|
)
|
||||||
created_by = models.ForeignKey(
|
created_by = models.ForeignKey(
|
||||||
User,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -743,7 +743,7 @@ class AlertInstance(models.Model):
|
|||||||
|
|
||||||
# Response tracking
|
# Response tracking
|
||||||
acknowledged_by = models.ForeignKey(
|
acknowledged_by = models.ForeignKey(
|
||||||
User,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -751,7 +751,7 @@ class AlertInstance(models.Model):
|
|||||||
help_text="User who acknowledged the alert"
|
help_text="User who acknowledged the alert"
|
||||||
)
|
)
|
||||||
resolved_by = models.ForeignKey(
|
resolved_by = models.ForeignKey(
|
||||||
User,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -938,7 +938,7 @@ class CommunicationChannel(models.Model):
|
|||||||
help_text="Last update timestamp"
|
help_text="Last update timestamp"
|
||||||
)
|
)
|
||||||
created_by = models.ForeignKey(
|
created_by = models.ForeignKey(
|
||||||
User,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=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.core.validators
|
||||||
import django.db.models.deletion
|
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.
@ -8,9 +8,11 @@ from django.urls import reverse
|
|||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from .models import (
|
from .models import (
|
||||||
Encounter, VitalSigns, ProblemList, CarePlan,
|
Encounter, VitalSigns, ProblemList, CarePlan,
|
||||||
ClinicalNote, NoteTemplate
|
ClinicalNote, NoteTemplate, Icd10
|
||||||
)
|
)
|
||||||
|
|
||||||
|
admin.site.register(Icd10)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Encounter)
|
@admin.register(Encounter)
|
||||||
class EncounterAdmin(admin.ModelAdmin):
|
class EncounterAdmin(admin.ModelAdmin):
|
||||||
@ -576,3 +578,5 @@ class ProblemListInline(admin.TabularInline):
|
|||||||
# Add inlines to Encounter admin
|
# Add inlines to Encounter admin
|
||||||
EncounterAdmin.inlines = [VitalSignsInline, ClinicalNoteInline, ProblemListInline]
|
EncounterAdmin.inlines = [VitalSignsInline, ClinicalNoteInline, ProblemListInline]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
77
emr/forms.py
77
emr/forms.py
@ -210,6 +210,18 @@ class CarePlanForm(forms.ModelForm):
|
|||||||
'approved', 'approved_by'
|
'approved', 'approved_by'
|
||||||
]
|
]
|
||||||
widgets = {
|
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'}),
|
'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'}),
|
'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'}),
|
'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
|
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):
|
class ClinicalNoteForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
Form for creating and updating clinical notes.
|
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.
582
emr/models.py
582
emr/models.py
@ -16,6 +16,52 @@ class Encounter(models.Model):
|
|||||||
"""
|
"""
|
||||||
Clinical encounter model for tracking patient visits and care episodes.
|
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 relationship
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
@ -50,40 +96,13 @@ class Encounter(models.Model):
|
|||||||
# Encounter Details
|
# Encounter Details
|
||||||
encounter_type = models.CharField(
|
encounter_type = models.CharField(
|
||||||
max_length=30,
|
max_length=30,
|
||||||
choices=[
|
choices=EncounterType.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'),
|
|
||||||
],
|
|
||||||
help_text='Type of encounter'
|
help_text='Type of encounter'
|
||||||
)
|
)
|
||||||
|
|
||||||
encounter_class = models.CharField(
|
encounter_class = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=EncounterClass.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'),
|
|
||||||
],
|
|
||||||
help_text='Encounter class (HL7 standard)'
|
help_text='Encounter class (HL7 standard)'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -100,18 +119,8 @@ class Encounter(models.Model):
|
|||||||
# Status
|
# Status
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=EncounterStatus.choices,
|
||||||
('PLANNED', 'Planned'),
|
default=EncounterStatus.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',
|
|
||||||
help_text='Current encounter status'
|
help_text='Current encounter status'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -162,14 +171,8 @@ class Encounter(models.Model):
|
|||||||
# Priority and Acuity
|
# Priority and Acuity
|
||||||
priority = models.CharField(
|
priority = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=Priority.choices,
|
||||||
('ROUTINE', 'Routine'),
|
default=Priority.ROUTINE,
|
||||||
('URGENT', 'Urgent'),
|
|
||||||
('ASAP', 'ASAP'),
|
|
||||||
('STAT', 'STAT'),
|
|
||||||
('EMERGENCY', 'Emergency'),
|
|
||||||
],
|
|
||||||
default='ROUTINE',
|
|
||||||
help_text='Encounter priority'
|
help_text='Encounter priority'
|
||||||
)
|
)
|
||||||
acuity_level = models.PositiveIntegerField(
|
acuity_level = models.PositiveIntegerField(
|
||||||
@ -272,6 +275,44 @@ class VitalSigns(models.Model):
|
|||||||
Vital signs model for tracking patient vital signs measurements.
|
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 relationship
|
||||||
encounter = models.ForeignKey(
|
encounter = models.ForeignKey(
|
||||||
Encounter,
|
Encounter,
|
||||||
@ -312,14 +353,7 @@ class VitalSigns(models.Model):
|
|||||||
)
|
)
|
||||||
temperature_method = models.CharField(
|
temperature_method = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=TemperatureMethod.choices,
|
||||||
('ORAL', 'Oral'),
|
|
||||||
('RECTAL', 'Rectal'),
|
|
||||||
('AXILLARY', 'Axillary'),
|
|
||||||
('TYMPANIC', 'Tympanic'),
|
|
||||||
('TEMPORAL', 'Temporal'),
|
|
||||||
('CORE', 'Core'),
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Temperature measurement method'
|
help_text='Temperature measurement method'
|
||||||
@ -340,25 +374,14 @@ class VitalSigns(models.Model):
|
|||||||
)
|
)
|
||||||
bp_position = models.CharField(
|
bp_position = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=BpPosition.choices,
|
||||||
('SITTING', 'Sitting'),
|
|
||||||
('STANDING', 'Standing'),
|
|
||||||
('LYING', 'Lying'),
|
|
||||||
('SUPINE', 'Supine'),
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Patient position during BP measurement'
|
help_text='Patient position during BP measurement'
|
||||||
)
|
)
|
||||||
bp_cuff_size = models.CharField(
|
bp_cuff_size = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=BpCuffSize.choices,
|
||||||
('SMALL', 'Small'),
|
|
||||||
('REGULAR', 'Regular'),
|
|
||||||
('LARGE', 'Large'),
|
|
||||||
('EXTRA_LARGE', 'Extra Large'),
|
|
||||||
('PEDIATRIC', 'Pediatric'),
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Blood pressure cuff size'
|
help_text='Blood pressure cuff size'
|
||||||
@ -373,12 +396,7 @@ class VitalSigns(models.Model):
|
|||||||
)
|
)
|
||||||
heart_rhythm = models.CharField(
|
heart_rhythm = models.CharField(
|
||||||
max_length=25,
|
max_length=25,
|
||||||
choices=[
|
choices=HeartRhythm.choices,
|
||||||
('REGULAR', 'Regular'),
|
|
||||||
('IRREGULAR', 'Irregular'),
|
|
||||||
('REGULARLY_IRREGULAR', 'Regularly Irregular'),
|
|
||||||
('IRREGULARLY_IRREGULAR', 'Irregularly Irregular'),
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Heart rhythm'
|
help_text='Heart rhythm'
|
||||||
@ -401,18 +419,8 @@ class VitalSigns(models.Model):
|
|||||||
)
|
)
|
||||||
oxygen_delivery = models.CharField(
|
oxygen_delivery = models.CharField(
|
||||||
max_length=30,
|
max_length=30,
|
||||||
choices=[
|
choices=OxygenDelivery.choices,
|
||||||
('ROOM_AIR', 'Room Air'),
|
default=OxygenDelivery.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',
|
|
||||||
help_text='Oxygen delivery method'
|
help_text='Oxygen delivery method'
|
||||||
)
|
)
|
||||||
oxygen_flow_rate = models.DecimalField(
|
oxygen_flow_rate = models.DecimalField(
|
||||||
@ -573,6 +581,58 @@ class ProblemList(models.Model):
|
|||||||
Problem list model for tracking patient problems and diagnoses.
|
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 relationship
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
'core.Tenant',
|
'core.Tenant',
|
||||||
@ -610,14 +670,7 @@ class ProblemList(models.Model):
|
|||||||
)
|
)
|
||||||
coding_system = models.CharField(
|
coding_system = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=CodingSystem.choices,
|
||||||
('ICD10', 'ICD-10'),
|
|
||||||
('ICD9', 'ICD-9'),
|
|
||||||
('SNOMED', 'SNOMED CT'),
|
|
||||||
('CPT', 'CPT'),
|
|
||||||
('LOINC', 'LOINC'),
|
|
||||||
('OTHER', 'Other'),
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Coding system used'
|
help_text='Coding system used'
|
||||||
@ -626,20 +679,7 @@ class ProblemList(models.Model):
|
|||||||
# Problem Classification
|
# Problem Classification
|
||||||
problem_type = models.CharField(
|
problem_type = models.CharField(
|
||||||
max_length=30,
|
max_length=30,
|
||||||
choices=[
|
choices=ProblemType.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'),
|
|
||||||
],
|
|
||||||
help_text='Type of problem'
|
help_text='Type of problem'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -659,42 +699,23 @@ class ProblemList(models.Model):
|
|||||||
# Severity and Priority
|
# Severity and Priority
|
||||||
severity = models.CharField(
|
severity = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=Severity.choices,
|
||||||
('MILD', 'Mild'),
|
|
||||||
('MODERATE', 'Moderate'),
|
|
||||||
('SEVERE', 'Severe'),
|
|
||||||
('CRITICAL', 'Critical'),
|
|
||||||
('UNKNOWN', 'Unknown'),
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Problem severity'
|
help_text='Problem severity'
|
||||||
)
|
)
|
||||||
priority = models.CharField(
|
priority = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=Priority.choices,
|
||||||
('LOW', 'Low'),
|
default=Priority.MEDIUM,
|
||||||
('MEDIUM', 'Medium'),
|
|
||||||
('HIGH', 'High'),
|
|
||||||
('URGENT', 'Urgent'),
|
|
||||||
],
|
|
||||||
default='MEDIUM',
|
|
||||||
help_text='Problem priority'
|
help_text='Problem priority'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=ProblemStatus.choices,
|
||||||
('ACTIVE', 'Active'),
|
default=ProblemStatus.ACTIVE,
|
||||||
('INACTIVE', 'Inactive'),
|
|
||||||
('RESOLVED', 'Resolved'),
|
|
||||||
('REMISSION', 'In Remission'),
|
|
||||||
('RECURRENCE', 'Recurrence'),
|
|
||||||
('RELAPSE', 'Relapse'),
|
|
||||||
('UNKNOWN', 'Unknown'),
|
|
||||||
],
|
|
||||||
default='ACTIVE',
|
|
||||||
help_text='Current problem status'
|
help_text='Current problem status'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -745,13 +766,7 @@ class ProblemList(models.Model):
|
|||||||
)
|
)
|
||||||
laterality = models.CharField(
|
laterality = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=Laterality.choices,
|
||||||
('LEFT', 'Left'),
|
|
||||||
('RIGHT', 'Right'),
|
|
||||||
('BILATERAL', 'Bilateral'),
|
|
||||||
('UNILATERAL', 'Unilateral'),
|
|
||||||
('NOT_APPLICABLE', 'Not Applicable'),
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Laterality'
|
help_text='Laterality'
|
||||||
@ -850,6 +865,48 @@ class CarePlan(models.Model):
|
|||||||
"""
|
"""
|
||||||
Care plan model for managing patient care coordination and goals.
|
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 relationship
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
@ -887,38 +944,15 @@ class CarePlan(models.Model):
|
|||||||
# Plan Type and Category
|
# Plan Type and Category
|
||||||
plan_type = models.CharField(
|
plan_type = models.CharField(
|
||||||
max_length=30,
|
max_length=30,
|
||||||
choices=[
|
choices=PlanType.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'),
|
|
||||||
],
|
|
||||||
help_text='Type of care plan'
|
help_text='Type of care plan'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
category = models.CharField(
|
category = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
choices=[
|
choices=PlanCategory.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'),
|
|
||||||
],
|
|
||||||
help_text='Care plan category'
|
help_text='Care plan category'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -940,30 +974,16 @@ class CarePlan(models.Model):
|
|||||||
# Status
|
# Status
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=PlanStatus.choices,
|
||||||
('DRAFT', 'Draft'),
|
default=PlanStatus.DRAFT,
|
||||||
('ACTIVE', 'Active'),
|
|
||||||
('ON_HOLD', 'On Hold'),
|
|
||||||
('COMPLETED', 'Completed'),
|
|
||||||
('CANCELLED', 'Cancelled'),
|
|
||||||
('ENTERED_IN_ERROR', 'Entered in Error'),
|
|
||||||
('UNKNOWN', 'Unknown'),
|
|
||||||
],
|
|
||||||
default='DRAFT',
|
|
||||||
help_text='Care plan status'
|
help_text='Care plan status'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Priority
|
# Priority
|
||||||
priority = models.CharField(
|
priority = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=PlanPriority.choices,
|
||||||
('LOW', 'Low'),
|
default=PlanPriority.ROUTINE,
|
||||||
('ROUTINE', 'Routine'),
|
|
||||||
('URGENT', 'Urgent'),
|
|
||||||
('ASAP', 'ASAP'),
|
|
||||||
('STAT', 'STAT'),
|
|
||||||
],
|
|
||||||
default='ROUTINE',
|
|
||||||
help_text='Care plan priority'
|
help_text='Care plan priority'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1143,6 +1163,46 @@ class ClinicalNote(models.Model):
|
|||||||
Clinical note model for documenting patient care and observations.
|
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 relationship
|
||||||
encounter = models.ForeignKey(
|
encounter = models.ForeignKey(
|
||||||
Encounter,
|
Encounter,
|
||||||
@ -1170,26 +1230,7 @@ class ClinicalNote(models.Model):
|
|||||||
# Note Details
|
# Note Details
|
||||||
note_type = models.CharField(
|
note_type = models.CharField(
|
||||||
max_length=30,
|
max_length=30,
|
||||||
choices=[
|
choices=NoteType.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'),
|
|
||||||
],
|
|
||||||
help_text='Type of clinical note'
|
help_text='Type of clinical note'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1235,16 +1276,8 @@ class ClinicalNote(models.Model):
|
|||||||
# Status and Workflow
|
# Status and Workflow
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=NoteStatus.choices,
|
||||||
('DRAFT', 'Draft'),
|
default=NoteStatus.DRAFT,
|
||||||
('IN_PROGRESS', 'In Progress'),
|
|
||||||
('COMPLETED', 'Completed'),
|
|
||||||
('SIGNED', 'Signed'),
|
|
||||||
('AMENDED', 'Amended'),
|
|
||||||
('CORRECTED', 'Corrected'),
|
|
||||||
('ENTERED_IN_ERROR', 'Entered in Error'),
|
|
||||||
],
|
|
||||||
default='DRAFT',
|
|
||||||
help_text='Note status'
|
help_text='Note status'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1260,13 +1293,7 @@ class ClinicalNote(models.Model):
|
|||||||
)
|
)
|
||||||
signature_method = models.CharField(
|
signature_method = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=[
|
choices=NoteSignatureMethod.choices,
|
||||||
('ELECTRONIC', 'Electronic Signature'),
|
|
||||||
('DIGITAL', 'Digital Signature'),
|
|
||||||
('BIOMETRIC', 'Biometric Signature'),
|
|
||||||
('PASSWORD', 'Password Authentication'),
|
|
||||||
('TOKEN', 'Token Authentication'),
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Method of signature'
|
help_text='Method of signature'
|
||||||
@ -1375,6 +1402,45 @@ class NoteTemplate(models.Model):
|
|||||||
Note template model for standardizing clinical documentation.
|
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 relationship
|
||||||
tenant = models.ForeignKey(
|
tenant = models.ForeignKey(
|
||||||
'core.Tenant',
|
'core.Tenant',
|
||||||
@ -1404,50 +1470,13 @@ class NoteTemplate(models.Model):
|
|||||||
# Template Type
|
# Template Type
|
||||||
note_type = models.CharField(
|
note_type = models.CharField(
|
||||||
max_length=30,
|
max_length=30,
|
||||||
choices=[
|
choices=NoteType.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'),
|
|
||||||
],
|
|
||||||
help_text='Type of note this template is for'
|
help_text='Type of note this template is for'
|
||||||
)
|
)
|
||||||
|
|
||||||
specialty = models.CharField(
|
specialty = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
choices=[
|
choices=NoteSpecialty.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'),
|
|
||||||
],
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Medical specialty'
|
help_text='Medical specialty'
|
||||||
@ -1543,3 +1572,32 @@ class NoteTemplate(models.Model):
|
|||||||
|
|
||||||
super().save(*args, **kwargs)
|
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>
|
</div>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<span class="badge care-plan-status-{{ care_plan.status|lower }} me-2">{{ care_plan.get_status_display }}</span>
|
<p class="fw-bold me-2">Status:
|
||||||
<span class="badge care-plan-priority-{{ care_plan.priority|lower }}">{{ care_plan.get_priority_display }} Priority</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<!-- end care plan header -->
|
<!-- end care plan header -->
|
||||||
@ -161,18 +167,18 @@
|
|||||||
|
|
||||||
<div class="panel-heading-btn">
|
<div class="panel-heading-btn">
|
||||||
<a href="{% url 'patients:patient_detail' care_plan.patient.id %}" class="btn btn-xs btn-outline-secondary me-2">
|
<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>
|
</a>
|
||||||
{% if not care_plan.approved %}
|
{% if not care_plan.approved %}
|
||||||
<a href="#" class="btn btn-xs btn-outline-success me-2" data-bs-toggle="modal" data-bs-target="#approveModal">
|
<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>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'emr:care_plan_update' care_plan.id %}" class="btn btn-xs btn-outline-warning me-2">
|
<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>
|
||||||
<a href="{% url 'emr:care_plan_delete' care_plan.id %}" class="btn btn-xs btn-outline-danger me-2">
|
<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>
|
||||||
<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-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-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
||||||
@ -448,7 +454,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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.
|
<i class="fa fa-info-circle me-2"></i> No objectives have been defined for this care plan.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -475,7 +481,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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.
|
<i class="fa fa-info-circle me-2"></i> No interventions have been defined for this care plan.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -497,7 +503,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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.
|
<i class="fa fa-info-circle me-2"></i> No activities have been defined for this care plan.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -549,7 +555,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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.
|
<i class="fa fa-info-circle me-2"></i> No evaluation criteria have been defined for this care plan.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -603,7 +609,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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.
|
<i class="fa fa-info-circle me-2"></i> No resources have been identified for this care plan.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -627,7 +633,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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.
|
<i class="fa fa-info-circle me-2"></i> No support systems have been identified for this care plan.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -654,7 +660,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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.
|
<i class="fa fa-info-circle me-2"></i> No outcomes have been recorded for this care plan.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -678,6 +684,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{% if care_plan.clinical_notes.all %}
|
{% if care_plan.clinical_notes.all %}
|
||||||
|
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
{% for note in care_plan.clinical_notes.all %}
|
{% for note in care_plan.clinical_notes.all %}
|
||||||
<div class="timeline-item">
|
<div class="timeline-item">
|
||||||
@ -702,7 +709,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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.
|
<i class="fa fa-info-circle me-2"></i> No clinical notes are associated with this care plan.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static custom_filters%}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if form.instance.id %}Edit Care Plan: {{ form.instance.title }}{% else %}New Care Plan{% endif %}
|
{% 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>
|
<h5 class="form-section-title">Basic Information</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3 required-field">
|
<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 }}
|
{{ form.patient }}
|
||||||
<label for="{{ form.patient.id_for_label }}">Patient</label>
|
|
||||||
</div>
|
|
||||||
{% if form.patient.errors %}
|
{% if form.patient.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.patient.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.patient.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3 required-field">
|
<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 }}
|
{{ form.title }}
|
||||||
<label for="{{ form.title.id_for_label }}">Care Plan Title</label>
|
|
||||||
</div>
|
|
||||||
{% if form.title.errors %}
|
{% if form.title.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.title.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.title.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -163,19 +159,15 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3 required-field">
|
<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 }}
|
{{ form.plan_type }}
|
||||||
<label for="{{ form.plan_type.id_for_label }}">Plan Type</label>
|
|
||||||
</div>
|
|
||||||
{% if form.plan_type.errors %}
|
{% if form.plan_type.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.plan_type.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.plan_type.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3 required-field">
|
<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 }}
|
{{ form.category }}
|
||||||
<label for="{{ form.category.id_for_label }}">Category</label>
|
|
||||||
</div>
|
|
||||||
{% if form.category.errors %}
|
{% if form.category.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.category.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.category.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -184,7 +176,7 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 mb-3">
|
<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 }}
|
{{ form.description }}
|
||||||
{% if form.description.errors %}
|
{% if form.description.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.description.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.description.errors }}</div>
|
||||||
@ -198,28 +190,22 @@
|
|||||||
<h5 class="form-section-title">Timeline</h5>
|
<h5 class="form-section-title">Timeline</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 mb-3 required-field">
|
<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 }}
|
{{ form.start_date }}
|
||||||
<label for="{{ form.start_date.id_for_label }}">Start Date</label>
|
|
||||||
</div>
|
|
||||||
{% if form.start_date.errors %}
|
{% if form.start_date.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.start_date.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.start_date.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-3">
|
<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 }}
|
{{ form.target_completion_date }}
|
||||||
<label for="{{ form.target_completion_date.id_for_label }}">Target Completion Date</label>
|
|
||||||
</div>
|
|
||||||
{% if form.target_completion_date.errors %}
|
{% if form.target_completion_date.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.target_completion_date.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.target_completion_date.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-3">
|
<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 }}
|
{{ form.end_date }}
|
||||||
<label for="{{ form.end_date.id_for_label }}">End Date</label>
|
|
||||||
</div>
|
|
||||||
{% if form.end_date.errors %}
|
{% if form.end_date.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.end_date.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.end_date.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -228,28 +214,22 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 mb-3">
|
<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 }}
|
{{ form.next_review_date }}
|
||||||
<label for="{{ form.next_review_date.id_for_label }}">Next Review Date</label>
|
|
||||||
</div>
|
|
||||||
{% if form.next_review_date.errors %}
|
{% if form.next_review_date.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.next_review_date.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.next_review_date.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-3 required-field">
|
<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 }}
|
{{ form.status }}
|
||||||
<label for="{{ form.status.id_for_label }}">Status</label>
|
|
||||||
</div>
|
|
||||||
{% if form.status.errors %}
|
{% if form.status.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.status.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.status.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 mb-3 required-field">
|
<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 }}
|
{{ form.priority }}
|
||||||
<label for="{{ form.priority.id_for_label }}">Priority</label>
|
|
||||||
</div>
|
|
||||||
{% if form.priority.errors %}
|
{% if form.priority.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.priority.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.priority.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -258,10 +238,8 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3 required-field">
|
<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 }}
|
{{ form.completion_percentage }}
|
||||||
<label for="{{ form.completion_percentage.id_for_label }}">Completion Percentage</label>
|
|
||||||
</div>
|
|
||||||
{% if form.completion_percentage.errors %}
|
{% if form.completion_percentage.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.completion_percentage.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.completion_percentage.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -271,8 +249,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="form-check form-switch mt-4">
|
<div class="form-check form-switch mt-4">
|
||||||
{{ form.approved }}
|
|
||||||
<label class="form-check-label" for="{{ form.approved.id_for_label }}">Approved</label>
|
<label class="form-check-label" for="{{ form.approved.id_for_label }}">Approved</label>
|
||||||
|
{{ form.approved }}
|
||||||
</div>
|
</div>
|
||||||
{% if form.approved.errors %}
|
{% if form.approved.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.approved.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.approved.errors }}</div>
|
||||||
@ -280,7 +258,7 @@
|
|||||||
<div class="approved-fields" style="display: none;">
|
<div class="approved-fields" style="display: none;">
|
||||||
<div class="form-floating mt-2">
|
<div class="form-floating mt-2">
|
||||||
{{ form.approved_by }}
|
{{ 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>
|
</div>
|
||||||
{% if form.approved_by.errors %}
|
{% if form.approved_by.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.approved_by.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.approved_by.errors }}</div>
|
||||||
@ -295,17 +273,15 @@
|
|||||||
<h5 class="form-section-title">Provider Information</h5>
|
<h5 class="form-section-title">Provider Information</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3 required-field">
|
<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 }}
|
{{ form.primary_provider }}
|
||||||
<label for="{{ form.primary_provider.id_for_label }}">Primary Provider</label>
|
|
||||||
</div>
|
|
||||||
{% if form.primary_provider.errors %}
|
{% if form.primary_provider.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.primary_provider.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.primary_provider.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="{{ form.care_team.id_for_label }}">Care Team</label>
|
<label class="form-label" for="{{ form.care_team.id_for_label }}">Care Team</label>
|
||||||
{{ form.care_team }}
|
{{ form.care_team|attr:'multiple' }}
|
||||||
{% if form.care_team.errors %}
|
{% if form.care_team.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.care_team.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.care_team.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -318,8 +294,8 @@
|
|||||||
<h5 class="form-section-title">Related Problems</h5>
|
<h5 class="form-section-title">Related Problems</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 mb-3">
|
<div class="col-md-12 mb-3">
|
||||||
<label for="{{ form.related_problems.id_for_label }}">Related Problems</label>
|
<label class="form-label" for="{{ form.related_problems.id_for_label }}">Related Problems</label>
|
||||||
{{ form.related_problems }}
|
{{ form.related_problems|attr:'multiple' }}
|
||||||
{% if form.related_problems.errors %}
|
{% if form.related_problems.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ form.related_problems.errors }}</div>
|
<div class="invalid-feedback d-block">{{ form.related_problems.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -362,15 +338,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="form-floating">
|
<label>Status</label>
|
||||||
<select class="form-select" name="goal_status[]">
|
<select class="form-select form-select-sm" name="goal_status[]">
|
||||||
<option value="not_started">Not Started</option>
|
<option value="not_started">Not Started</option>
|
||||||
<option value="in_progress">In Progress</option>
|
<option value="in_progress">In Progress</option>
|
||||||
<option value="completed">Completed</option>
|
<option value="completed">Completed</option>
|
||||||
<option value="cancelled">Cancelled</option>
|
<option value="cancelled">Cancelled</option>
|
||||||
</select>
|
</select>
|
||||||
<label>Status</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -413,15 +388,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="form-floating">
|
<label>Status</label>
|
||||||
<select class="form-select" name="objective_status[]">
|
<select class="form-select" name="objective_status[]">
|
||||||
<option value="not_started">Not Started</option>
|
<option value="not_started">Not Started</option>
|
||||||
<option value="in_progress">In Progress</option>
|
<option value="in_progress">In Progress</option>
|
||||||
<option value="completed">Completed</option>
|
<option value="completed">Completed</option>
|
||||||
<option value="cancelled">Cancelled</option>
|
<option value="cancelled">Cancelled</option>
|
||||||
</select>
|
</select>
|
||||||
<label>Status</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -468,15 +441,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="form-floating">
|
<label>Status</label>
|
||||||
<select class="form-select" name="intervention_status[]">
|
<select class="form-select" name="intervention_status[]">
|
||||||
<option value="not_started">Not Started</option>
|
<option value="not_started">Not Started</option>
|
||||||
<option value="in_progress">In Progress</option>
|
<option value="in_progress">In Progress</option>
|
||||||
<option value="completed">Completed</option>
|
<option value="completed">Completed</option>
|
||||||
<option value="cancelled">Cancelled</option>
|
<option value="cancelled">Cancelled</option>
|
||||||
</select>
|
</select>
|
||||||
<label>Status</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -63,9 +63,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Care Plans Table -->
|
<!-- Care Plans Table -->
|
||||||
|
<div class="card">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-sm table-hover mb-0 align-middle">
|
||||||
<thead>
|
<thead class="table-primary">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Patient</th>
|
<th>Patient</th>
|
||||||
<th>Care Plan</th>
|
<th>Care Plan</th>
|
||||||
@ -145,16 +146,17 @@
|
|||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if plan.status == 'ACTIVE' %}
|
{% 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>
|
<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>
|
||||||
{# <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 %}
|
{% endif %}
|
||||||
<button class="btn btn-outline-secondary" title="Notes">
|
<button class="btn btn-outline-secondary" title="Notes">
|
||||||
<i class="fas fa-sticky-note"></i>
|
<i class="fas fa-sticky-note"></i>
|
||||||
@ -180,6 +182,7 @@
|
|||||||
{% include 'partial/pagination.html' %}
|
{% include 'partial/pagination.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</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 title %}{{ problem.problem_name }} | Problem Detail{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% 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" />
|
<link href="{% static 'plugins/bootstrap-icons/font/bootstrap-icons.css' %}" rel="stylesheet" />
|
||||||
<style>
|
<style>
|
||||||
.problem-badge {
|
.problem-badge {
|
||||||
@ -12,10 +12,10 @@
|
|||||||
padding: 0.35em 0.65em;
|
padding: 0.35em 0.65em;
|
||||||
}
|
}
|
||||||
.problem-header {
|
.problem-header {
|
||||||
background: linear-gradient(45deg, var(--bs-indigo), var(--bs-purple));
|
background: linear-gradient(90deg, var(--bs-black), var(--bs-secondary));
|
||||||
color: white;
|
color: white;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.25rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
.problem-status-active {
|
.problem-status-active {
|
||||||
@ -71,310 +71,316 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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 -->
|
<div class="container-fluid">
|
||||||
<h1 class="page-header">Problem Detail <small>Comprehensive problem information</small></h1>
|
<div class="row">
|
||||||
<!-- end page-header -->
|
<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 -->
|
<!-- begin panel -->
|
||||||
<div class="row">
|
<div class="panel panel-inverse">
|
||||||
<!-- begin col-12 -->
|
<div class="panel-heading">
|
||||||
<div class="col-xl-12">
|
<h4 class="panel-title">Problem Information</h4>
|
||||||
<!-- begin problem header -->
|
<div class="panel-heading-btn">
|
||||||
<div class="problem-header d-flex justify-content-between align-items-center">
|
<a href="{% url 'patients:patient_detail' problem.patient.id %}" class="btn btn-xs btn-outline-secondary me-2">
|
||||||
<div>
|
<i class="fa fa-user me-1"></i> Patient Profile
|
||||||
<h2 class="mb-1">{{ problem.problem_name }}</h2>
|
</a>
|
||||||
<div class="d-flex align-items-center">
|
{% if problem.status != 'RESOLVED' and problem.status != 'REMISSION' %}
|
||||||
<span class="me-3">
|
<a href="#" class="btn btn-xs btn-outline-success me-2" data-bs-toggle="modal" data-bs-target="#resolveModal">
|
||||||
<i class="bi bi-person-fill me-1"></i> {{ problem.patient.get_full_name }}
|
<i class="fa fa-check-circle me-1"></i>Mark as Resolved
|
||||||
</span>
|
</a>
|
||||||
<span class="me-3">
|
{% endif %}
|
||||||
<i class="bi bi-calendar-event me-1"></i> Onset: {{ problem.onset_date|default:"Not specified" }}
|
<a href="{% url 'emr:problem_update' problem.id %}" class="btn btn-xs btn-outline-warning me-2">
|
||||||
</span>
|
<i class="fa fa-edit me-1"></i>Edit
|
||||||
{% if problem.problem_code %}
|
</a>
|
||||||
<span>
|
<a href="{% url 'emr:problem_delete' problem.id %}" class="btn btn-xs btn-outline-danger me-2">
|
||||||
<i class="bi bi-upc-scan me-1"></i> {{ problem.get_coding_system_display }}: {{ problem.problem_code }}
|
<i class="fa fa-trash me-1"></i>Delete
|
||||||
</span>
|
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex">
|
<!-- end related care plans panel -->
|
||||||
<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 -->
|
<!-- begin clinical notes panel -->
|
||||||
<div class="panel panel-inverse">
|
<div class="panel panel-inverse">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h4 class="panel-title">Problem Information</h4>
|
<h4 class="panel-title">Related Clinical Notes</h4>
|
||||||
<div class="panel-heading-btn">
|
<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-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-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-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
||||||
</div>
|
<a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
|
||||||
</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>
|
</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>
|
<div class="panel-body">
|
||||||
</div>
|
{% if problem.related_clinical_notes.all %}
|
||||||
<!-- end panel -->
|
<div class="timeline">
|
||||||
|
{% for note in problem.related_clinical_notes.all %}
|
||||||
<!-- begin related care plans panel -->
|
<div class="timeline-item">
|
||||||
<div class="panel panel-inverse">
|
<div class="timeline-time">
|
||||||
<div class="panel-heading">
|
{{ note.note_datetime|date:"M d, Y" }} <small>{{ note.note_datetime|time:"h:i A" }}</small>
|
||||||
<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>
|
</div>
|
||||||
<div class="card-body">
|
<div class="timeline-icon">
|
||||||
<p class="card-text">{{ care_plan.description|truncatechars:100 }}</p>
|
<a href="javascript:;"> </a>
|
||||||
<div class="progress mb-2" style="height: 10px;">
|
</div>
|
||||||
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: {{ care_plan.completion_percentage }}%">{{ care_plan.completion_percentage }}%</div>
|
<div class="timeline-content">
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<small class="text-muted">{{ care_plan.get_plan_type_display }}</small>
|
<h5>{{ note.title }}</h5>
|
||||||
<small class="text-muted">{{ care_plan.start_date|date:"M d, Y" }}</small>
|
<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>
|
</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>
|
</div>
|
||||||
{% endfor %}
|
{% else %}
|
||||||
</div>
|
<div class="alert alert-info">
|
||||||
{% else %}
|
<i class="fa fa-info-circle me-2"></i> No clinical notes are associated with this problem.
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
</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>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- end clinical notes panel -->
|
||||||
<!-- 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>
|
</div>
|
||||||
<!-- end action buttons -->
|
|
||||||
</div>
|
</div>
|
||||||
<!-- end col-12 -->
|
|
||||||
</div>
|
</div>
|
||||||
<!-- end row -->
|
|
||||||
|
|
||||||
<!-- begin resolve modal -->
|
<!-- begin resolve modal -->
|
||||||
<div class="modal fade" id="resolveModal" tabindex="-1" aria-labelledby="resolveModalLabel" aria-hidden="true">
|
<div class="modal fade" id="resolveModal" tabindex="-1" aria-labelledby="resolveModalLabel" aria-hidden="true">
|
||||||
@ -415,6 +421,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
|
<script src="{% static 'plugins/lity/dist/lity.min.js' %}"></script>
|
||||||
<script src="{% static 'plugins/moment/min/moment.min.js' %}"></script>
|
<script src="{% static 'plugins/moment/min/moment.min.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|||||||
@ -4,165 +4,174 @@
|
|||||||
{% block title %}Problem List - {{ block.super }}{% endblock %}
|
{% block title %}Problem List - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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="container-fluid">
|
||||||
<div class="row">
|
<div class="panel panel-inverse" data-sortable-id="index-1">
|
||||||
<div class="col-12">
|
<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">
|
||||||
<div class="card-header">
|
<div class="table-responsive">
|
||||||
<h4 class="card-title mb-0">
|
<table class="table table-sm table-hover mb-0 align-middle">
|
||||||
<i class="fas fa-list me-2"></i>Problem List Management
|
<thead class="table-primary">
|
||||||
</h4>
|
<tr>
|
||||||
</div>
|
<th>Patient</th>
|
||||||
|
<th>Problem</th>
|
||||||
<div class="card-body">
|
<th>Type</th>
|
||||||
<!-- Filters -->
|
<th>Priority</th>
|
||||||
<div class="row mb-3">
|
<th>Status</th>
|
||||||
<div class="col-md-2">
|
<th>Onset Date</th>
|
||||||
<select name="problem_type" class="form-select">
|
<th>Diagnosed By</th>
|
||||||
<option value="">All Types</option>
|
<th>Actions</th>
|
||||||
{% for value, label in problem_types %}
|
</tr>
|
||||||
<option value="{{ value }}">{{ label }}</option>
|
</thead>
|
||||||
{% endfor %}
|
<tbody>
|
||||||
</select>
|
{% for problem in problems %}
|
||||||
</div>
|
<tr>
|
||||||
<div class="col-md-2">
|
<td>
|
||||||
<select name="status" class="form-select">
|
<strong>{{ problem.patient.get_full_name }}</strong><br>
|
||||||
<option value="">All Status</option>
|
<small class="text-muted">
|
||||||
{% for value, label in problem_statuses %}
|
MRN: {{ problem.patient.mrn }} •
|
||||||
<option value="{{ value }}">{{ label }}</option>
|
{{ problem.patient.age }}y {{ problem.patient.get_gender_display }}
|
||||||
{% endfor %}
|
</small>
|
||||||
</select>
|
</td>
|
||||||
</div>
|
<td>
|
||||||
<div class="col-md-2">
|
<strong>{{ problem.problem_name }}</strong>
|
||||||
<select name="priority" class="form-select">
|
{% if problem.problem_code %}
|
||||||
<option value="">All Priorities</option>
|
<br><small class="text-muted">Code: {{ problem.problem_code }}</small>
|
||||||
{% for value, label in priorities %}
|
{% endif %}
|
||||||
<option value="{{ value }}">{{ label }}</option>
|
{% if problem.description %}
|
||||||
{% endfor %}
|
<br><small class="text-muted">{{ problem.description|truncatechars:50 }}</small>
|
||||||
</select>
|
{% endif %}
|
||||||
</div>
|
</td>
|
||||||
<div class="col-md-4">
|
<td>{{ problem.get_problem_type_display }}</td>
|
||||||
<input type="text" name="search" class="form-control" placeholder="Search patients, problems, codes...">
|
<td>
|
||||||
</div>
|
{% if problem.priority == 'HIGH' %}
|
||||||
<div class="col-md-2">
|
<span class="badge bg-danger">High</span>
|
||||||
<button type="submit" class="btn btn-primary w-100">
|
{% elif problem.priority == 'MEDIUM' %}
|
||||||
<i class="fas fa-search"></i>
|
<span class="badge bg-warning">Medium</span>
|
||||||
</button>
|
{% else %}
|
||||||
</div>
|
<span class="badge bg-info">Low</span>
|
||||||
</div>
|
{% endif %}
|
||||||
|
</td>
|
||||||
<!-- Problems Table -->
|
<td>
|
||||||
<div class="table-responsive">
|
{% if problem.status == 'ACTIVE' %}
|
||||||
<table class="table table-hover">
|
<span class="badge bg-success">Active</span>
|
||||||
<thead>
|
{% elif problem.status == 'RESOLVED' %}
|
||||||
<tr>
|
<span class="badge bg-secondary">Resolved</span>
|
||||||
<th>Patient</th>
|
{% elif problem.status == 'INACTIVE' %}
|
||||||
<th>Problem</th>
|
<span class="badge bg-secondary">Inactive</span>
|
||||||
<th>Type</th>
|
{% elif problem.status == 'RULED_OUT' %}
|
||||||
<th>Priority</th>
|
<span class="badge bg-danger">Ruled Out</span>
|
||||||
<th>Status</th>
|
{% endif %}
|
||||||
<th>Onset Date</th>
|
</td>
|
||||||
<th>Diagnosed By</th>
|
<td>
|
||||||
<th>Actions</th>
|
{{ problem.onset_date|date:"M d, Y" }}
|
||||||
</tr>
|
{% if problem.resolution_date %}
|
||||||
</thead>
|
<br><small class="text-muted">
|
||||||
<tbody>
|
Resolved: {{ problem.resolution_date|date:"M d, Y" }}
|
||||||
{% 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>
|
</small>
|
||||||
</td>
|
{% endif %}
|
||||||
<td>
|
</td>
|
||||||
<strong>{{ problem.problem_name }}</strong>
|
<td>
|
||||||
{% if problem.problem_code %}
|
{{ problem.diagnosing_provider.get_full_name }}
|
||||||
<br><small class="text-muted">Code: {{ problem.problem_code }}</small>
|
{% if problem.managing_provider and problem.managing_provider != problem.diagnosing_provider %}
|
||||||
{% endif %}
|
<br><small class="text-muted">
|
||||||
{% if problem.description %}
|
Managing: {{ problem.managing_provider.get_full_name }}
|
||||||
<br><small class="text-muted">{{ problem.description|truncatechars:50 }}</small>
|
</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ problem.get_problem_type_display }}</td>
|
<td>
|
||||||
<td>
|
<div class="btn-group btn-group-sm">
|
||||||
{% if problem.priority == 'HIGH' %}
|
<a href="{% url 'emr:problem_detail' problem.id %}" class="btn btn-outline-primary" title="View Details">
|
||||||
<span class="badge bg-danger">High</span>
|
<i class="fas fa-eye"></i>
|
||||||
{% elif problem.priority == 'MEDIUM' %}
|
</a>
|
||||||
<span class="badge bg-warning">Medium</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="badge bg-info">Low</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if problem.status == 'ACTIVE' %}
|
{% if problem.status == 'ACTIVE' %}
|
||||||
<span class="badge bg-success">Active</span>
|
<button class="btn btn-outline-success"
|
||||||
{% elif problem.status == 'RESOLVED' %}
|
title="Mark Resolved"
|
||||||
<span class="badge bg-secondary">Resolved</span>
|
hx-post="{% url 'emr:resolve_problem' problem.id %}"
|
||||||
{% elif problem.status == 'INACTIVE' %}
|
hx-confirm="Mark this problem as resolved?"
|
||||||
<span class="badge bg-secondary">Inactive</span>
|
hx-swap="none"
|
||||||
{% elif problem.status == 'RULED_OUT' %}
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
||||||
<span class="badge bg-danger">Ruled Out</span>
|
<i class="fas fa-check"></i>
|
||||||
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</td>
|
||||||
{{ problem.onset_date|date:"M d, Y" }}
|
</tr>
|
||||||
{% if problem.resolution_date %}
|
{% empty %}
|
||||||
<br><small class="text-muted">
|
<tr>
|
||||||
Resolved: {{ problem.resolution_date|date:"M d, Y" }}
|
<td colspan="8" class="text-center py-4">
|
||||||
</small>
|
<i class="fas fa-list fa-3x text-muted mb-3"></i>
|
||||||
{% endif %}
|
<h5 class="text-muted">No problems found</h5>
|
||||||
</td>
|
<p class="text-muted">No problems match your current filters.</p>
|
||||||
<td>
|
</td>
|
||||||
{{ problem.diagnosing_provider.get_full_name }}
|
</tr>
|
||||||
{% if problem.managing_provider and problem.managing_provider != problem.diagnosing_provider %}
|
{% endfor %}
|
||||||
<br><small class="text-muted">
|
</tbody>
|
||||||
Managing: {{ problem.managing_provider.get_full_name }}
|
</table>
|
||||||
</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>
|
</div>
|
||||||
|
<!-- Pagination -->
|
||||||
|
{% if is_paginated %}
|
||||||
|
{% include 'partial/pagination.html' %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>/', 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>/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>/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/', views.ClinicalNoteListView.as_view(), name='clinical_note_list'),
|
||||||
path('notes/<int:pk>/', views.ClinicalNoteDetailView.as_view(), name='clinical_note_detail'),
|
path('notes/<int:pk>/', views.ClinicalNoteDetailView.as_view(), name='clinical_note_detail'),
|
||||||
@ -50,6 +53,10 @@ urlpatterns = [
|
|||||||
path('note/<int:note_id>/sign/', views.sign_note, name='sign_note'),
|
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('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
|
# API endpoints
|
||||||
# path('api/', include('emr.api.urls')),
|
# 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