This commit is contained in:
Marwan Alwali 2025-08-19 16:22:19 +03:00
parent fd2f7259c0
commit f941a8e608
12 changed files with 1387 additions and 131 deletions

Binary file not shown.

View File

@ -262,7 +262,48 @@ class Bed(models.Model):
"""
Hospital bed model for tracking individual bed status and assignments.
"""
BED_TYPE_CHOICES = [
('STANDARD', 'Standard Bed'),
('ICU', 'ICU Bed'),
('CARDIAC', 'Cardiac Monitoring'),
('ISOLATION', 'Isolation Bed'),
('BARIATRIC', 'Bariatric Bed'),
('PEDIATRIC', 'Pediatric Bed'),
('NEONATAL', 'Neonatal Bed'),
('MATERNITY', 'Maternity Bed'),
('PSYCHIATRIC', 'Psychiatric Bed'),
('STRETCHER', 'Stretcher/Gurney'),
('RECLINER', 'Recliner Chair'),
('OTHER', 'Other'),
]
ROOM_TYPE_CHOICES = [
('PRIVATE', 'Private Room'),
('SEMI_PRIVATE', 'Semi-Private'),
('SHARED', 'Shared Room'),
('ICU', 'ICU Room'),
('ISOLATION', 'Isolation Room'),
]
STATUS_CHOICES = [
('AVAILABLE', 'Available'),
('OCCUPIED', 'Occupied'),
('RESERVED', 'Reserved'),
('MAINTENANCE', 'Under Maintenance'),
('CLEANING', 'Being Cleaned'),
('OUT_OF_ORDER', 'Out of Order'),
('BLOCKED', 'Blocked'),
]
CLEANING_LEVEL_CHOICES = [
('STANDARD', 'Standard Cleaning'),
('DEEP', 'Deep Cleaning'),
('ISOLATION', 'Isolation Cleaning'),
('TERMINAL', 'Terminal Cleaning'),
]
BED_POSITION_CHOICES = [
('WINDOW', 'Window Side'),
('DOOR', 'Door Side'),
('CENTER', 'Center'),
('CORNER', 'Corner'),
]
# Ward relationship
ward = models.ForeignKey(
Ward,
@ -284,48 +325,21 @@ class Bed(models.Model):
# Bed Type and Configuration
bed_type = models.CharField(
max_length=30,
choices=[
('STANDARD', 'Standard Bed'),
('ICU', 'ICU Bed'),
('CARDIAC', 'Cardiac Monitoring'),
('ISOLATION', 'Isolation Bed'),
('BARIATRIC', 'Bariatric Bed'),
('PEDIATRIC', 'Pediatric Bed'),
('NEONATAL', 'Neonatal Bed'),
('MATERNITY', 'Maternity Bed'),
('PSYCHIATRIC', 'Psychiatric Bed'),
('STRETCHER', 'Stretcher/Gurney'),
('RECLINER', 'Recliner Chair'),
('OTHER', 'Other'),
],
choices=BED_TYPE_CHOICES,
default='STANDARD',
help_text='Type of bed'
)
room_type = models.CharField(
max_length=20,
choices=[
('PRIVATE', 'Private Room'),
('SEMI_PRIVATE', 'Semi-Private'),
('SHARED', 'Shared Room'),
('ICU', 'ICU Room'),
('ISOLATION', 'Isolation Room'),
],
choices=ROOM_TYPE_CHOICES,
help_text='Type of room'
)
# Bed Status
status = models.CharField(
max_length=20,
choices=[
('AVAILABLE', 'Available'),
('OCCUPIED', 'Occupied'),
('RESERVED', 'Reserved'),
('MAINTENANCE', 'Under Maintenance'),
('CLEANING', 'Being Cleaned'),
('OUT_OF_ORDER', 'Out of Order'),
('BLOCKED', 'Blocked'),
],
choices=STATUS_CHOICES,
default='AVAILABLE',
help_text='Current bed status'
)
@ -405,12 +419,7 @@ class Bed(models.Model):
)
cleaning_level = models.CharField(
max_length=20,
choices=[
('STANDARD', 'Standard Cleaning'),
('DEEP', 'Deep Cleaning'),
('ISOLATION', 'Isolation Cleaning'),
('TERMINAL', 'Terminal Cleaning'),
],
choices=CLEANING_LEVEL_CHOICES,
blank=True,
null=True,
help_text='Level of last cleaning'
@ -439,12 +448,7 @@ class Bed(models.Model):
# Location Details
bed_position = models.CharField(
max_length=20,
choices=[
('WINDOW', 'Window Side'),
('DOOR', 'Door Side'),
('CENTER', 'Center'),
('CORNER', 'Corner'),
],
choices=BED_POSITION_CHOICES,
blank=True,
null=True,
help_text='Position within room'

View File

@ -403,6 +403,22 @@ def create_saudi_transfers(admissions):
to_bed = random.choice(available_beds)
to_ward = to_bed.ward
# Get users who can request transfers (physicians, nurses, etc.)
requesting_users = User.objects.filter(
tenant=admission.tenant,
is_active=True,
role__in=['PHYSICIAN', 'NURSE', 'NURSE_PRACTITIONER', 'PHYSICIAN_ASSISTANT']
)
if not requesting_users.exists():
# Fallback to any active user from the tenant
requesting_users = User.objects.filter(tenant=admission.tenant, is_active=True)
if not requesting_users.exists():
continue # Skip if no users available
requested_by = random.choice(requesting_users)
requested_datetime = admission.admission_datetime + timedelta(hours=random.randint(1, 72))
status = random.choice(transfer_statuses)
@ -415,6 +431,15 @@ def create_saudi_transfers(admissions):
if status in ['in_progress', 'completed']:
actual_datetime = scheduled_datetime + timedelta(minutes=random.randint(-30, 60))
# Get transport team members
transport_team_members = []
nurses = User.objects.filter(
tenant=admission.tenant,
role__in=['NURSE', 'NURSE_PRACTITIONER']
)
if nurses:
transport_team_members = random.sample(list(nurses), min(2, nurses.count()))
transfer = Transfer.objects.create(
transfer_id=uuid.uuid4(),
admission=admission,
@ -424,6 +449,7 @@ def create_saudi_transfers(admissions):
from_bed=admission.current_bed,
to_ward=to_ward,
to_bed=to_bed,
requested_by=requested_by, # Add the required field
requested_datetime=requested_datetime,
scheduled_datetime=scheduled_datetime,
actual_datetime=actual_datetime,
@ -435,7 +461,6 @@ def create_saudi_transfers(admissions):
]),
priority=random.choice(priorities),
transport_method=random.choice(transport_methods),
transport_team=['Nursing staff', 'Transport aide'] if random.choice([True, False]) else [],
equipment_needed=['IV pole', 'Oxygen tank', 'Monitor'] if random.choice([True, False]) else [],
supplies_needed=['Medications', 'Patient chart', 'Personal items'] if random.choice([True, False]) else [],
patient_condition=random.choice(['stable', 'unstable', 'critical', 'improving']),
@ -455,6 +480,10 @@ def create_saudi_transfers(admissions):
updated_at=requested_datetime + timedelta(hours=random.randint(1, 24))
)
# Set transport team using .set() method for many-to-many relationship
if transport_team_members:
transfer.transport_team.set(transport_team_members)
# Update admission current location if transfer completed
if status == 'completed':
admission.current_ward = to_ward
@ -570,7 +599,7 @@ def create_saudi_discharge_summaries(admissions):
social_worker_involved=random.choice([True, False]),
case_manager_involved=random.choice([True, False]),
readmission_risk=random.choice(['low', 'moderate', 'high']),
patient_satisfaction=random.choice(['excellent', 'good', 'fair', 'poor']),
patient_satisfaction=random.randint(1, 5), # Changed to numeric scale (1-5)
discharging_physician=admission.attending_physician,
summary_completed=True,
summary_signed=True,
@ -623,12 +652,14 @@ def create_saudi_surgery_schedules(tenants, surgeries_per_tenant=30):
nurses = list(User.objects.filter(tenant=tenant, role__in=['NURSE', 'NURSE_PRACTITIONER']))
admissions = list(Admission.objects.filter(tenant=tenant, status='active'))
if not patients or not surgeons or not nurses:
if not patients or not surgeons or not nurses or not admissions:
print(f"Insufficient data for tenant {tenant.name}: patients={len(patients)}, surgeons={len(surgeons)}, nurses={len(nurses)}, admissions={len(admissions)}")
continue
for i in range(surgeries_per_tenant):
patient = random.choice(patients)
admission = random.choice(admissions) if admissions and random.choice([True, False]) else None
for i in range(min(surgeries_per_tenant, len(admissions))): # Don't exceed available admissions
# Always use an admission - required field
admission = random.choice(admissions)
patient = admission.patient # Use patient from the admission
procedure = random.choice(SAUDI_SURGICAL_PROCEDURES)
# Generate surgery date (within next 30 days)
@ -653,7 +684,7 @@ def create_saudi_surgery_schedules(tenants, surgeries_per_tenant=30):
tenant=tenant,
surgery_id=uuid.uuid4(),
patient=patient,
admission=admission,
admission=admission, # Always provide admission - required field
procedure_name=procedure,
procedure_code=f"CPT-{random.randint(10000, 99999)}",
surgery_type=random.choice(surgery_types)[0],
@ -693,14 +724,14 @@ def create_saudi_surgery_schedules(tenants, surgeries_per_tenant=30):
updated_at=django_timezone.now() - timedelta(hours=random.randint(1, 24))
)
# Set assistant surgeons
# Set assistant surgeons using .set() method for many-to-many relationship
assistant_surgeons = random.sample(surgeons, random.randint(0, 2))
if assistant_surgeons:
surgery.assistant_surgeons.set(assistant_surgeons)
surgeries.append(surgery)
print(f"Created {surgeries_per_tenant} surgeries for {tenant.name}")
print(f"Created {min(surgeries_per_tenant, len(admissions))} surgeries for {tenant.name}")
return surgeries

File diff suppressed because it is too large Load Diff

View File

@ -393,7 +393,7 @@ class MedicationListView(LoginRequiredMixin, ListView):
if active_only:
queryset = queryset.filter(is_active=True)
return queryset.order_by('name')
return queryset.order_by('brand_name')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

View File

@ -336,33 +336,7 @@
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Bed pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1">&laquo; First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Last &raquo;</a>
</li>
{% endif %}
</ul>
</nav>
{% include 'partial/pagination.html' %}
{% endif %}
</div>
</div>

View File

@ -148,8 +148,8 @@
</style>
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<div class="container">
<div class="container-fluid">
<ul class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'inpatients:dashboard' %}">Inpatients</a></li>
@ -578,7 +578,7 @@
</form>
</div>
</div>
</div>
{% endblock %}
{% block js %}

View File

@ -150,13 +150,13 @@
</button>
{% if transfer.status not in 'COMPLETED,CANCELLED,REJECTED' %}
<button class="btn btn-outline-secondary"
title="Cancel"
hx-post="{% url 'inpatients:cancel_transfer' transfer.id %}"
hx-confirm="Cancel this transfer?"
hx-swap="none">
<i class="fas fa-ban"></i>
</button>
{# <button class="btn btn-outline-secondary" #}
{# title="Cancel"#}
{# hx-post="{% url 'inpatients:cancel_transfer' transfer.id %}"#}
{# hx-confirm="Cancel this transfer?"#}
{# hx-swap="none">#}
{# <i class="fas fa-ban"></i>#}
{# </button>#}
{% endif %}
</div>
</td>

View File

@ -300,35 +300,35 @@
</div>
</td>
<td>
<span class="badge bg-secondary">{{ item.location.name }}</span>
<span class="badge bg-secondary">{{ item.storage_location }}</span>
{% if item.location.description %}
<br><small class="text-muted">{{ item.location.description }}</small>
{% endif %}
</td>
<td>
<div class="d-flex align-items-center">
<span class="fw-bold {% if item.is_low_stock %}text-warning{% elif item.current_stock == 0 %}text-danger{% else %}text-success{% endif %}">
{{ item.current_stock }}
<span class="fw-bold {% if item.is_low_stock %}text-warning{% elif item.quantity_available == 0 %}text-danger{% else %}text-success{% endif %}">
{{ item.quantity_available }}
</span>
<span class="text-muted ms-1">{{ item.get_unit_of_measure_display }}</span>
<span class="text-muted ms-1">{{ item.bin_location }}</span>
</div>
{% if item.is_low_stock %}
<small class="text-warning">
<i class="fas fa-exclamation-triangle me-1"></i>Low Stock
</small>
{% elif item.current_stock == 0 %}
{% elif item.quantity_available == 0 %}
<small class="text-danger">
<i class="fas fa-times-circle me-1"></i>Out of Stock
</small>
{% endif %}
</td>
<td>
<span class="text-muted">{{ item.minimum_stock_level }}</span>
<span class="text-muted">{{ item.reorder_point }}</span>
</td>
<td>
<span class="font-monospace">{{ item.lot_number }}</span>
{% if item.supplier %}
<br><small class="text-muted">{{ item.supplier.name }}</small>
<br><small class="text-muted">{{ item.supplier }}</small>
{% endif %}
</td>
<td>
@ -347,8 +347,8 @@
</td>
<td>
{% if item.unit_cost %}
<div>${{ item.unit_cost|floatformat:2 }}</div>
<small class="text-muted">Total: ${{ item.total_value|floatformat:2 }}</small>
<div><span class="symbol">&#xea;</span>{{ item.unit_cost|floatformat:'2g' }}</div>
<small class="text-muted">Total: <span class="symbol">&#xea;</span>{{ item.total_cost|floatformat:'2g' }}</small>
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
@ -356,7 +356,7 @@
<td>
{% if item.is_expired %}
<span class="badge bg-danger">Expired</span>
{% elif item.current_stock == 0 %}
{% elif item.quantity_available == 0 %}
<span class="badge bg-danger">Out of Stock</span>
{% elif item.is_low_stock %}
<span class="badge bg-warning text-dark">Low Stock</span>
@ -396,36 +396,8 @@
</div>
<!-- Pagination -->
{% if page_obj.has_other_pages %}
<div class="card-footer">
<nav aria-label="Inventory pagination">
<ul class="pagination justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Previous</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Next</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
</div>
</div>