This commit is contained in:
Marwan Alwali 2025-09-21 17:38:18 +03:00
parent 4d06ca4b5e
commit 94e180f776
67 changed files with 11303 additions and 3109 deletions

BIN
.DS_Store vendored

Binary file not shown.

Binary file not shown.

View File

@ -67,10 +67,10 @@
</div> </div>
</div> </div>
<!-- Problems Table --> <!-- Problems Table -->
<div class="card"> <div class="card border border-primary-subtle">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-sm table-hover mb-0 align-middle"> <table class="table table-sm table-hover mb-0 align-middle">
<thead class="table-primary"> <thead class="table-primary ">
<tr> <tr>
<th>Patient</th> <th>Patient</th>
<th>Problem</th> <th>Problem</th>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -69,10 +69,8 @@ class RoomForm(forms.ModelForm):
class Meta: class Meta:
model = Room model = Room
fields = [ fields = [
'floor', 'room_number', 'name', 'room_type', 'area_sqm', 'floor', 'room_number', 'name', 'area_sqm',
'capacity', 'occupancy_status', 'is_accessible', 'current_tenant', 'capacity', 'occupancy_status', 'is_accessible', 'notes'
'lease_start_date', 'lease_end_date', 'monthly_rent',
'has_hvac', 'has_electrical', 'has_plumbing', 'has_internet', 'notes'
] ]
widgets = { widgets = {
'floor': forms.Select(attrs={'class': 'form-select'}), 'floor': forms.Select(attrs={'class': 'form-select'}),
@ -81,26 +79,13 @@ class RoomForm(forms.ModelForm):
'room_type': forms.Select(attrs={'class': 'form-select'}), 'room_type': forms.Select(attrs={'class': 'form-select'}),
'area_sqm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}), 'area_sqm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'capacity': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}), 'capacity': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
'occupancy_status': forms.Select(attrs={'class': 'form-select'}),
'is_accessible': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'is_accessible': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'current_tenant': forms.TextInput(attrs={'class': 'form-control'}),
'lease_start_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'lease_end_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'monthly_rent': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'has_hvac': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'has_electrical': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'has_plumbing': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'has_internet': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
} }
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
lease_start = cleaned_data.get('lease_start_date')
lease_end = cleaned_data.get('lease_end_date')
if lease_start and lease_end and lease_start >= lease_end:
raise ValidationError('Lease end date must be after lease start date.')
return cleaned_data return cleaned_data
@ -132,7 +117,7 @@ class AssetForm(forms.ModelForm):
class Meta: class Meta:
model = Asset model = Asset
fields = [ fields = [
'asset_id', 'name', 'category', 'building', 'floor', 'room', 'name', 'category', 'building', 'floor', 'room',
'location_description', 'manufacturer', 'model', 'serial_number', 'location_description', 'manufacturer', 'model', 'serial_number',
'purchase_date', 'purchase_cost', 'current_value', 'depreciation_rate', 'purchase_date', 'purchase_cost', 'current_value', 'depreciation_rate',
'warranty_start_date', 'warranty_end_date', 'service_provider', 'warranty_start_date', 'warranty_end_date', 'service_provider',
@ -140,7 +125,6 @@ class AssetForm(forms.ModelForm):
'last_inspection_date', 'next_maintenance_date', 'assigned_to', 'notes' 'last_inspection_date', 'next_maintenance_date', 'assigned_to', 'notes'
] ]
widgets = { widgets = {
'asset_id': forms.TextInput(attrs={'class': 'form-control'}),
'name': forms.TextInput(attrs={'class': 'form-control'}), 'name': forms.TextInput(attrs={'class': 'form-control'}),
'category': forms.Select(attrs={'class': 'form-select'}), 'category': forms.Select(attrs={'class': 'form-select'}),
'building': forms.Select(attrs={'class': 'form-select'}), 'building': forms.Select(attrs={'class': 'form-select'}),
@ -271,7 +255,7 @@ class MaintenanceScheduleForm(forms.ModelForm):
model = MaintenanceSchedule model = MaintenanceSchedule
fields = [ fields = [
'name', 'description', 'maintenance_type', 'asset', 'building', 'name', 'description', 'maintenance_type', 'asset', 'building',
'room', 'frequency', 'frequency_interval', 'start_date', 'room', 'frequency_interval', 'start_date',
'end_date', 'assigned_to', 'estimated_duration_hours', 'is_active' 'end_date', 'assigned_to', 'estimated_duration_hours', 'is_active'
] ]
widgets = { widgets = {
@ -322,8 +306,7 @@ class VendorForm(forms.ModelForm):
model = Vendor model = Vendor
fields = [ fields = [
'name', 'vendor_type', 'contact_person', 'email', 'phone', 'name', 'vendor_type', 'contact_person', 'email', 'phone',
'address', 'license_number', 'insurance_policy', 'address', 'crn', 'vrn', 'rating', 'is_active'
'insurance_expiry', 'rating', 'is_active'
] ]
widgets = { widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}), 'name': forms.TextInput(attrs={'class': 'form-control'}),
@ -332,9 +315,8 @@ class VendorForm(forms.ModelForm):
'email': forms.EmailInput(attrs={'class': 'form-control'}), 'email': forms.EmailInput(attrs={'class': 'form-control'}),
'phone': forms.TextInput(attrs={'class': 'form-control'}), 'phone': forms.TextInput(attrs={'class': 'form-control'}),
'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'license_number': forms.TextInput(attrs={'class': 'form-control'}), 'crn': forms.TextInput(attrs={'class': 'form-control'}),
'insurance_policy': forms.TextInput(attrs={'class': 'form-control'}), 'vrn': forms.TextInput(attrs={'class': 'form-control'}),
'insurance_expiry': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'rating': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0', 'max': '5'}), 'rating': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0', 'max': '5'}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
} }

View File

@ -0,0 +1,633 @@
# facility/management/commands/seed_facility_saudi.py
import random
import uuid
from datetime import datetime, timedelta, date
from decimal import Decimal
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth import get_user_model
from django.db import transaction
from django.utils import timezone
# TODO: Change this import path to your app's models module
from facility_management.models import (
Building, Floor, Room,
AssetCategory, Asset,
MaintenanceType, MaintenanceRequest, MaintenanceSchedule,
Vendor, ServiceContract,
Inspection, EnergyMeter, EnergyReading,
SpaceReservation,
)
from core.models import Tenant # Adjust if your Tenant lives elsewhere
SAUDI_CITIES = [
("Riyadh", 24.7136, 46.6753, [
"Al Olaya", "Al Malqa", "Al Nakheel", "Al Sulaymaniyah", "Hittin", "Al Yasmin", "Al Wurud"
]),
("Jeddah", 21.4858, 39.1925, [
"Al Rawdah", "Al Nahda", "Al Andalus", "Al Zahra", "Al Basateen", "Al Salama"
]),
("Dammam", 26.3927, 49.9777, [
"Al Faisaliyah", "Al Shati", "Al Badiyah", "Al Mazruiyah", "An Nakheel"
]),
("Makkah", 21.3891, 39.8579, [
"Al Awali", "Aziziyah", "Al Naseem", "Al Shara'i", "Al Kaakiya"
]),
("Madinah", 24.5247, 39.5692, [
"Quba", "Qurban", "Al Khalidiyyah", "Al Azhari", "Al Rabwa"
]),
("Khobar", 26.2794, 50.2083, [
"Al Aqrabiyah", "Al Khobar Al Shamaliyah", "Al Yarmouk", "Al Rakah"
]),
]
# Lightweight Saudi-flavored suppliers
VENDOR_NAMES = [
"Saudi HVAC Co.", "Riyadh Fire Systems", "Gulf Electrical Services",
"Najd Facilities Cleaning", "Red Sea Security", "Eastern Landscaping",
"Hijaz Plumbing & Drainage", "Nusuk IT Services", "Al Haramain Maintenance"
]
SERVICE_AREAS_SNIPPETS = [
"Riyadh HQ campus, central plant and clinics",
"Jeddah airport satellite buildings and hangars",
"Dammam logistics & cold-storage facilities",
"Makkah accommodation & hospitality zones",
"Madinah clinical towers and outpatient blocks",
]
ASSET_CATEGORIES = [
("HVAC Systems", "HVAC", "Chillers, AHUs, FCUs, pumps"),
("Electrical", "ELEC", "Transformers, switchgear, UPS"),
("Medical Equipment", "MED", "Imaging, patient monitors, ventilators"),
("IT Equipment", "IT", "Servers, switches, wireless APs"),
("Fire & Safety", "FIRE", "Fire pump, alarm panels, detectors"),
("Plumbing", "PLB", "Pumps, tanks, RO/softeners"),
]
MAINT_TYPES = [
("Preventive - HVAC", "PM_HVAC", "Quarterly PM for AHUs, filters, belts"),
("Fire Alarm Test (SASO)", "FIRE_TEST", "Monthly fire alarm & pump tests"),
("Elevator Inspection", "LIFT_INSP", "Quarterly inspection per standard"),
("Water Quality Check", "WATER_QA", "Legionella sampling & flushing"),
("Electrical IR Scan", "ELEC_IR", "Annual IR thermography of panels"),
("Generator Load Test", "GEN_TEST", "Monthly DG set test under load"),
]
ASSET_MODELS = [
("Trane", "RTAC-300", "CHILLER"),
("Siemens", "SIVACON-S8", "SWITCHGEAR"),
("Philips", "IntelliVue MX700", "PATIENT_MONITOR"),
("Cisco", "Catalyst 9300", "SWITCH"),
("Honeywell", "Notifier NFS2-3030", "FIRE_ALARM"),
("Grundfos", "CR-32-10", "PUMP"),
]
def rand_decimal(a, b, precision="0.01"):
q = Decimal(precision)
return (Decimal(a) + (Decimal(random.random()) * (Decimal(b) - Decimal(a)))).quantize(q)
def rand_phone():
# Saudi mobile format: 05x-xxxxxxx
return f"05{random.randint(0,9)}-{random.randint(1000000,9999999)}"
def rand_email(name):
base = name.lower().replace(" ", ".")
return f"{base}@example.sa"
def rand_crn():
# KSA CRN often represented with 10 digits
return f"{random.randint(10**9, 10**10 - 1)}"
def rand_vrn():
# KSA VAT/TRN is 15 digits
return f"{random.randint(10**14, 10**15 - 1)}"
def safe_choice(seq):
return random.choice(seq) if seq else None
def ensure_users_for_tenant(tenant):
"""
Try to fetch existing users for the tenant; if not found, create a small set.
Works for custom AUTH_USER_MODEL that likely has a 'tenant' FK/field.
"""
User = get_user_model()
qs = User.objects.all()
# If your user model has 'tenant', filter by it. Otherwise, just use any user.
if hasattr(User, "tenant"):
qs = qs.filter(tenant=tenant)
users = list(qs[:10])
if users:
return users
# Create a small staff set (adjust fields if your User requires more)
seed_names = [
("mohammed.fm", "Mohammed", "Al-Qahtani"),
("ibrahim.bme", "Ibrahim", "Al-Otaibi"),
("abdullah.main", "Abdullah", "Al-Zahrani"),
("nora.admin", "Nora", "Al-Anazi"),
("aisha.ops", "Aisha", "Al-Qurashi"),
]
created = []
for uname, first, last in seed_names:
kwargs = dict(
username=uname,
first_name=first,
last_name=last,
email=f"{uname}@tenhal.sa",
)
if hasattr(User, "tenant"):
kwargs["tenant"] = tenant
try:
user = User.objects.create_user(**kwargs, password="Pass1234!")
except TypeError:
# Fallback if create_user signature differs
user = User.objects.create(**kwargs)
user.set_password("Pass1234!")
user.save()
created.append(user)
return created
def unique_building_code(base, tenant_id):
# Make globally-unique code (your model has unique=True)
suffix = 1
while True:
code = f"T{tenant_id}-{base}-{suffix:02d}"
if not Building.objects.filter(code=code).exists():
return code
suffix += 1
def make_building(tenant, city_tuple, facility_manager):
city, lat, lng, districts = city_tuple
district = safe_choice(districts)
code_base = city.upper()[:3]
code = unique_building_code(code_base, tenant.id)
b = Building.objects.create(
tenant=tenant,
name=f"{city} {district or 'Campus'}",
code=code,
building_type=safe_choice([ct[0] for ct in Building.BuildingType.choices]),
address=f"{district or 'District'}, {city}, Saudi Arabia",
latitude=Decimal(f"{lat + random.uniform(-0.05, 0.05):.6f}"),
longitude=Decimal(f"{lng + random.uniform(-0.05, 0.05):.6f}"),
floor_count=random.randint(2, 10),
total_area_sqm=rand_decimal(5000, 80000),
construction_year=random.choice(range(1990, date.today().year + 1)),
architect="Dar Al Riyadh",
contractor="Saudi Binladin Group",
facility_manager=facility_manager,
)
return b
def make_floors_and_rooms(building, floors=5, rooms_per_floor=20):
floor_objs = []
for n in range(floors):
f = Floor.objects.create(
building=building,
floor_number=n,
name=f"Level {n}",
area_sqm=rand_decimal(1000, 20000),
ceiling_height_m=rand_decimal(2.7, 4.2, "0.10"),
is_public_access=(n == 0),
)
floor_objs.append(f)
room_objs = []
for r in range(rooms_per_floor):
rn = f"{n:02d}{r+1:03d}"
room = Room.objects.create(
floor=f,
room_number=rn,
name=random.choice(["Clinic", "Office", "Storage", "Server", "Lab", "Ward", "Meeting"]),
area_sqm=rand_decimal(12, 120),
capacity=random.choice([None, 2, 4, 6, 10, 20]),
occupancy_status=safe_choice([s[0] for s in Room.OccupancyStatus.choices]),
is_accessible=True,
notes=""
)
room_objs.append(room)
return floor_objs
def seed_asset_categories():
results = []
for name, code, desc in ASSET_CATEGORIES:
obj, _ = AssetCategory.objects.get_or_create(code=code, defaults=dict(name=name, description=desc))
if obj.name != name or obj.description != desc:
obj.name = name
obj.description = desc
obj.save()
results.append(obj)
# set some parents (simple tree)
hvac = next((c for c in results if c.code == "HVAC"), None)
fire = next((c for c in results if c.code == "FIRE"), None)
if fire and hvac and fire.parent_category_id is None:
fire.parent_category = hvac
fire.save()
return results
def seed_maintenance_types():
results = []
for name, code, desc in MAINT_TYPES:
obj, _ = MaintenanceType.objects.get_or_create(code=code, defaults=dict(name=name, description=desc))
if obj.name != name or obj.description != desc:
obj.name = name
obj.description = desc
obj.save()
results.append(obj)
return results
def make_assets(buildings, categories, users, count=80):
results = []
for _ in range(count):
building = safe_choice(buildings)
floor = safe_choice(list(Floor.objects.filter(building=building)))
room = safe_choice(list(Room.objects.filter(floor=floor))) if floor else None
manufacturer, model, _family = safe_choice(ASSET_MODELS)
purchase_years_ago = random.randint(0, 12)
purchase_date = timezone.now().date() - timedelta(days=365 * purchase_years_ago + random.randint(0, 364))
warranty_years = random.choice([1, 2, 3, 5])
warranty_start = purchase_date
warranty_end = warranty_start + timedelta(days=365 * warranty_years)
a = Asset.objects.create(
name=f"{manufacturer} {model}",
category=safe_choice(categories),
building=building,
floor=floor,
room=room,
location_description=f"Near {room.name if room else 'corridor'}",
manufacturer=manufacturer,
model=model,
serial_number=f"{manufacturer[:3].upper()}-{uuid.uuid4().hex[:10].upper()}",
purchase_date=purchase_date,
purchase_cost=rand_decimal(15000, 1500000, "1.00"),
current_value=rand_decimal(5000, 1200000, "1.00"),
depreciation_rate=Decimal("10.00"),
warranty_start_date=warranty_start,
warranty_end_date=warranty_end,
service_provider=safe_choice([v for v in VENDOR_NAMES]),
service_contract_number=f"SC-{timezone.now().year}-{random.randint(1000,9999)}",
status=safe_choice([s[0] for s in Asset.AssetStatus.choices]),
condition=safe_choice([c[0] for c in Asset.AssetCondition.choices]),
last_inspection_date=timezone.now().date() - timedelta(days=random.randint(5, 180)),
next_maintenance_date=timezone.now().date() + timedelta(days=random.randint(10, 120)),
assigned_to=safe_choice(users),
notes=""
)
results.append(a)
return results
def make_vendors(tenant, count=8):
results = []
for i in range(count):
vname = VENDOR_NAMES[i % len(VENDOR_NAMES)]
contact = f"{vname.split()[0]} Rep"
obj, _ = Vendor.objects.get_or_create(
tenant=tenant, name=vname,
defaults=dict(
vendor_type=safe_choice([t[0] for t in Vendor.VendorType.choices]),
contact_person=contact,
email=rand_email(vname.replace(" ", "")),
phone=rand_phone(),
address=f"{safe_choice(SAUDI_CITIES)[0]} {safe_choice(['Business Park','Industrial Area','Tech Valley'])}",
crn=rand_crn(),
vrn=rand_vrn(),
rating=rand_decimal("3.20", "4.90", "0.10"),
total_contracts=random.randint(1, 30),
is_active=True
)
)
results.append(obj)
return results
def make_contracts(vendors, buildings, users, count=6):
results = []
for _ in range(count):
ven = safe_choice(vendors)
start = timezone.now().date() - timedelta(days=random.randint(0, 400))
end = start + timedelta(days=random.randint(180, 1095))
cn = f"CTR-{ven.id}-{random.randint(1000,9999)}"
sc = ServiceContract.objects.create(
contract_number=cn,
vendor=ven,
title=f"{ven.name} Comprehensive Service",
description="Preventive & corrective maintenance aligned with Vision 2030 reliability goals.",
start_date=start,
end_date=end,
contract_value=rand_decimal(100000, 5000000, "1.00"),
payment_terms="Net 30 days",
service_areas=safe_choice(SERVICE_AREAS_SNIPPETS),
status=safe_choice([c[0] for c in ServiceContract.ContractStatus.choices]),
auto_renewal=random.choice([True, False]),
renewal_notice_days=random.choice([30, 45, 60]),
contract_manager=safe_choice(users),
notes=""
)
# Link to 13 buildings
sc.buildings.add(*random.sample(buildings, k=min(len(buildings), random.randint(1, 3))))
results.append(sc)
return results
def make_maintenance_requests(assets, mtypes, users, per_asset=1):
results = []
for asset in assets:
for _ in range(per_asset):
req_by = safe_choice(users)
assigned = safe_choice(users)
mr = MaintenanceRequest.objects.create(
title=f"{asset.name} {safe_choice(['Noise','Leak','Alarm','Performance drop','Vibration'])}",
description="Auto-generated test request reflecting typical O&M issues.",
maintenance_type=safe_choice(mtypes),
building=asset.building,
floor=asset.floor,
room=asset.room,
asset=asset,
priority=safe_choice([p[0] for p in MaintenanceRequest.Priority.choices]),
status=safe_choice([s[0] for s in MaintenanceRequest.MaintenanceStatus.choices]),
requested_by=req_by,
assigned_to=assigned if random.random() > 0.3 else None,
estimated_hours=rand_decimal("1.00", "16.00", "0.25"),
estimated_cost=rand_decimal(250, 10000, "1.00"),
actual_cost=None,
notes="",
completion_notes=""
)
results.append(mr)
return results
def make_schedules(assets, mtypes, users, count=60):
results = []
combos = set()
for _ in range(count):
asset = safe_choice(assets)
building = asset.building
room = asset.room
key = (asset.id, building.id, room.id if room else None)
if key in combos:
continue
combos.add(key)
freq = safe_choice([f[0] for f in MaintenanceSchedule.FrequencyInterval.choices])
start = timezone.now().date() - timedelta(days=random.randint(0, 365))
est = rand_decimal("1.00", "8.00", "0.25")
sched = MaintenanceSchedule.objects.create(
name=f"{asset.name} {safe_choice(['PM','Inspection','Service'])}",
description="Auto-generated PM program",
maintenance_type=safe_choice(mtypes),
asset=asset,
building=building,
room=room,
frequency_interval=freq,
start_date=start,
end_date=None,
assigned_to=safe_choice(users),
estimated_duration_hours=est,
is_active=True,
last_generated_date=None,
next_due_date=start + timedelta(days=freq),
)
results.append(sched)
return results
def make_inspections(buildings, users, count=20):
results = []
for _ in range(count):
b = safe_choice(buildings)
scheduled = timezone.now() + timedelta(days=random.randint(-60, 60))
insp = Inspection.objects.create(
inspection_id="", # let .save generate
inspection_type=safe_choice([t[0] for t in Inspection.InspectionType.choices]),
title=f"{b.code} {safe_choice(['Quarterly','Annual','Random'])} Inspection",
description="Safety & compliance per local authority guidance.",
building=b,
scheduled_date=scheduled,
estimated_duration_hours=rand_decimal("1.00", "6.00", "0.25"),
inspector=safe_choice(users),
inspector_external=random.choice(["", "TUV Middle East", "Saudi Safety Org", "SASO Certifier"]),
inspector_organization=random.choice(["", "SASO", "Civil Defense", "Third-Party"]),
status=safe_choice([s[0] for s in Inspection.Status.choices]),
started_date=None,
completed_date=None,
overall_rating=random.choice(["", "Pass", "Fail", "Conditional"]),
findings="",
recommendations="",
requires_followup=random.choice([True, False]),
followup_date=None,
)
# relate a few floors/rooms/assets
floors = list(Floor.objects.filter(building=b))
if floors:
insp.floors.add(*random.sample(floors, k=min(len(floors), random.randint(1, 3))))
rooms = list(Room.objects.filter(floor__in=floors))
if rooms:
insp.rooms.add(*random.sample(rooms, k=min(len(rooms), random.randint(1, 5))))
assets = list(Asset.objects.filter(building=b))
if assets:
insp.assets.add(*random.sample(assets, k=min(len(assets), random.randint(1, 5))))
results.append(insp)
return results
def make_energy_meters(buildings):
"""
Create 24 meters per building (electricity + water + optional others)
"""
results = []
meter_types = [EnergyMeter.MeterType.ELECTRICITY, EnergyMeter.MeterType.WATER,
EnergyMeter.MeterType.GAS, EnergyMeter.MeterType.CHILLED_WATER]
for b in buildings:
for mt in random.sample(meter_types, k=random.randint(2, 4)):
meter_id = f"MTR-{b.code}-{mt[:2]}-{random.randint(1000,9999)}"
em = EnergyMeter.objects.create(
meter_id=meter_id,
meter_type=mt,
building=b,
location_description=f"Utility Room {safe_choice(['A','B','C'])}",
manufacturer=safe_choice(["Schneider", "Siemens", "ABB", "Yokogawa"]),
model=f"{safe_choice(['Q1','E45','HX','Prime'])}-{random.randint(100,999)}",
serial_number=uuid.uuid4().hex[:12].upper(),
installation_date=timezone.now().date() - timedelta(days=random.randint(100, 2000)),
current_reading=Decimal("0.00"),
last_reading_date=None,
is_active=True,
calibration_date=timezone.now().date() - timedelta(days=random.randint(100, 800)),
next_calibration_date=timezone.now().date() + timedelta(days=random.randint(100, 800)),
)
results.append(em)
return results
def make_energy_readings(meters, days=60, users=None):
"""
Generate monotonic readings over past N days. Electricity in kWh, water in m3, etc.
"""
results = []
users = users or []
reader = safe_choice(users)
for m in meters:
base = rand_decimal(1000, 50000, "1.00")
daily_incr = rand_decimal(50, 1500, "1.00")
ts = timezone.now() - timedelta(days=days)
for d in range(days):
ts += timedelta(days=1)
base += daily_incr + rand_decimal(-0.20, 0.20, "0.01") * daily_incr # mild variation
er = EnergyReading.objects.create(
meter=m,
reading_date=ts,
reading_value=base.quantize(Decimal("1.00")),
consumption=None, # model .save() derives consumption
cost=None,
read_by=reader,
is_estimated=random.random() < 0.1,
notes=""
)
results.append(er)
return results
def make_reservations(rooms, users, count=50):
results = []
for _ in range(count):
room = safe_choice(rooms)
reserved_by = safe_choice(users)
start = timezone.now() + timedelta(days=random.randint(-15, 30), hours=random.randint(8, 17))
duration_h = random.choice([1, 2, 3, 4, 6, 8])
end = start + timedelta(hours=duration_h)
hourly_rate = random.choice([None, Decimal("150.00"), Decimal("300.00")])
sr = SpaceReservation.objects.create(
reservation_id="", # let .save generate
room=room,
title=f"{safe_choice(['Training', 'Meeting', 'Workshop', 'Orientation'])} {room.name}",
description=f"Auto-generated booking for {room.floor.building.code}",
start_datetime=start,
end_datetime=end,
reserved_by=reserved_by,
contact_person=f"{reserved_by.first_name or 'Contact'} {reserved_by.last_name or ''}".strip(),
contact_email=reserved_by.email or rand_email("contact"),
contact_phone=rand_phone(),
expected_attendees=random.randint(3, 30),
setup_requirements=random.choice(["Theater", "U-Shape", "Classroom", "Boardroom"]),
catering_required=random.random() < 0.3,
av_equipment_required=random.random() < 0.6,
status=safe_choice([s[0] for s in SpaceReservation.ReservationStatus.choices]),
approved_by=safe_choice(users) if random.random() < 0.7 else None,
approved_at=timezone.now() if random.random() < 0.7 else None,
hourly_rate=hourly_rate,
total_cost=None, # model .save() computes if hourly_rate is provided
notes=""
)
results.append(sr)
return results
class Command(BaseCommand):
help = (
"Seed Saudi-influenced, multi-tenant facility data (buildings, floors/rooms, assets, "
"maintenance, vendors/contracts, inspections, meters/readings, reservations).\n\n"
"Examples:\n"
" python manage.py seed_facility_saudi --tenant 1\n"
" python manage.py seed_facility_saudi --tenant 1 --buildings 3 --floors 6 --rooms-per-floor 25 --assets 120 --seed 42 --purge\n"
)
def add_arguments(self, parser):
parser.add_argument("--tenant", required=True, help="Tenant ID or slug.")
parser.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility.")
parser.add_argument("--buildings", type=int, default=4)
parser.add_argument("--floors", type=int, default=5)
parser.add_argument("--rooms-per-floor", type=int, default=20)
parser.add_argument("--assets", type=int, default=80)
parser.add_argument("--purge", action="store_true", help="Delete existing tenant data first.")
def handle(self, *args, **opts):
if opts["seed"] is not None:
random.seed(opts["seed"])
tenant = self._get_tenant(opts["tenant"])
if not tenant:
raise CommandError("Tenant not found.")
User = get_user_model()
users = ensure_users_for_tenant(tenant)
building_count = max(1, opts["buildings"])
floors = max(1, opts["floors"])
rpf = max(5, opts["rooms_per_floor"])
asset_target = max(10, opts["assets"])
with transaction.atomic():
if opts["purge"]:
self._purge_tenant_data(tenant)
self.stdout.write(self.style.WARNING(f"Purged existing data for tenant {tenant}."))
# Seed lookup tables
categories = seed_asset_categories()
mtypes = seed_maintenance_types()
# Buildings, floors, rooms
buildings = []
for i in range(building_count):
city_tuple = safe_choice(SAUDI_CITIES)
fm = safe_choice(users)
b = make_building(tenant, city_tuple, fm)
buildings.append(b)
make_floors_and_rooms(b, floors=floors, rooms_per_floor=rpf)
# Vendors & contracts
vendors = make_vendors(tenant, count=min(10, len(VENDOR_NAMES)))
contracts = make_contracts(vendors, buildings, users, count=6)
# Assets
assets = make_assets(buildings, categories, users, count=asset_target)
# Maintenance
mreqs = make_maintenance_requests(assets, mtypes, users, per_asset=1)
msched = make_schedules(assets, mtypes, users, count=min(60, len(assets)))
# Inspections
insps = make_inspections(buildings, users, count=20)
# Energy meters and readings
meters = make_energy_meters(buildings)
mreads = make_energy_readings(meters, days=60, users=users)
# Reservations
rooms = list(Room.objects.filter(floor__building__in=buildings))
resvs = make_reservations(rooms, users, count=50)
self.stdout.write(self.style.SUCCESS(
f"Seed complete for tenant {tenant}:\n"
f" Buildings: {len(buildings)} (Floors x Rooms: {floors} x {rpf})\n"
f" Vendors: {len(vendors)} | Contracts: {len(contracts)}\n"
f" Asset Categories: {len(categories)} | Assets: {len(assets)}\n"
f" Maint. Types: {len(mtypes)} | Requests: {len(mreqs)} | Schedules: {len(msched)}\n"
f" Inspections: {len(insps)}\n"
f" Energy Meters: {len(meters)} | Readings: {len(mreads)}\n"
f" Reservations: {len(resvs)}"
))
def _get_tenant(self, ident_or_slug):
# Try pk then slug
try:
return Tenant.objects.get(pk=int(ident_or_slug))
except (Tenant.DoesNotExist, ValueError):
pass
try:
return Tenant.objects.get(slug=ident_or_slug)
except Tenant.DoesNotExist:
return None
def _purge_tenant_data(self, tenant):
# Order matters due to FK relations
SpaceReservation.objects.filter(room__floor__building__tenant=tenant).delete()
EnergyReading.objects.filter(meter__building__tenant=tenant).delete()
EnergyMeter.objects.filter(building__tenant=tenant).delete()
Inspection.objects.filter(building__tenant=tenant).delete()
MaintenanceSchedule.objects.filter(building__tenant=tenant).delete()
MaintenanceRequest.objects.filter(building__tenant=tenant).delete()
Asset.objects.filter(building__tenant=tenant).delete()
ServiceContract.objects.filter(buildings__tenant=tenant).delete()
Vendor.objects.filter(tenant=tenant).delete()
Room.objects.filter(floor__building__tenant=tenant).delete()
Floor.objects.filter(building__tenant=tenant).delete()
Building.objects.filter(tenant=tenant).delete()

View File

@ -61,7 +61,7 @@ class Building(models.Model):
return 0 return 0
occupied_rooms = Room.objects.filter( occupied_rooms = Room.objects.filter(
floor__building=self, floor__building=self,
occupancy_status='occupied' occupancy_status=Room.OccupancyStatus.OCCUPIED
).count() ).count()
return (occupied_rooms / total_rooms) * 100 return (occupied_rooms / total_rooms) * 100

View File

@ -268,7 +268,7 @@
<!-- Pagination --> <!-- Pagination -->
{% if is_paginated %} {% if is_paginated %}
{% include 'pagination.html' %} {% include 'partial/pagination.html' %}
{% endif %} {% endif %}
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-5">

View File

@ -11,7 +11,7 @@ urlpatterns = [
path('buildings/', views.BuildingListView.as_view(), name='building_list'), path('buildings/', views.BuildingListView.as_view(), name='building_list'),
path('buildings/create/', views.BuildingCreateView.as_view(), name='building_create'), path('buildings/create/', views.BuildingCreateView.as_view(), name='building_create'),
path('buildings/<int:pk>/', views.BuildingDetailView.as_view(), name='building_detail'), path('buildings/<int:pk>/', views.BuildingDetailView.as_view(), name='building_detail'),
path('buildings/<int:pk>/edit/', views.BuildingUpdateView.as_view(), name='building_edit'), path('buildings/<int:pk>/edit/', views.BuildingUpdateView.as_view(), name='building_update'),
# Asset Management # Asset Management
path('assets/', views.AssetListView.as_view(), name='asset_list'), path('assets/', views.AssetListView.as_view(), name='asset_list'),

View File

@ -29,7 +29,7 @@ class FacilityDashboardView(LoginRequiredMixin, TemplateView):
context['stats'] = { context['stats'] = {
'total_buildings': Building.objects.filter(is_active=True).count(), 'total_buildings': Building.objects.filter(is_active=True).count(),
'total_assets': Asset.objects.count(), 'total_assets': Asset.objects.count(),
'operational_assets': Asset.objects.filter(status='operational').count(), 'operational_assets': Asset.objects.filter(status=Asset.AssetStatus.OPERATIONAL).count(),
'assets_needing_maintenance': Asset.objects.filter(next_maintenance_date__lte=today).count(), 'assets_needing_maintenance': Asset.objects.filter(next_maintenance_date__lte=today).count(),
'open_work_orders': MaintenanceRequest.objects.filter(status__in=['submitted', 'assigned', 'in_progress']).count(), 'open_work_orders': MaintenanceRequest.objects.filter(status__in=['submitted', 'assigned', 'in_progress']).count(),
'completed_today': MaintenanceRequest.objects.filter(completed_date__date=today).count(), 'completed_today': MaintenanceRequest.objects.filter(completed_date__date=today).count(),
@ -90,7 +90,7 @@ class BuildingListView(LoginRequiredMixin, ListView):
def get_queryset(self): def get_queryset(self):
tenant = self.request.user.tenant tenant = self.request.user.tenant
queryset = Building.objects.filter(tenant=tenant).order_by('airport_code', 'name') queryset = Building.objects.filter(tenant=tenant).order_by( 'name')
# Search functionality # Search functionality
search = self.request.GET.get('search') search = self.request.GET.get('search')
@ -107,16 +107,16 @@ class BuildingListView(LoginRequiredMixin, ListView):
queryset = queryset.filter(building_type=building_type) queryset = queryset.filter(building_type=building_type)
# Filter by airport # Filter by airport
airport = self.request.GET.get('airport') # airport = self.request.GET.get('airport')
if airport: # if airport:
queryset = queryset.filter(airport_code=airport) # queryset = queryset.filter(airport_code=airport)
return queryset return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['building_types'] = Building.BuildingType.choices context['building_types'] = Building.BuildingType.choices
context['airports'] = Building.objects.values_list('airport_code', flat=True).distinct() # context['airports'] = Building.objects.values_list('airport_code', flat=True).distinct()
return context return context

View File

@ -40,6 +40,7 @@ urlpatterns += i18n_patterns(
path('blood-bank/', include('blood_bank.urls')), path('blood-bank/', include('blood_bank.urls')),
path('patients/', include('patients.urls')), path('patients/', include('patients.urls')),
path('appointments/', include('appointments.urls')), path('appointments/', include('appointments.urls')),
path('facility-management/', include('facility_management.urls')),
path('inpatients/', include('inpatients.urls')), path('inpatients/', include('inpatients.urls')),
path('emr/', include('emr.urls')), path('emr/', include('emr.urls')),
path('pharmacy/', include('pharmacy.urls')), path('pharmacy/', include('pharmacy.urls')),

Binary file not shown.

Binary file not shown.

View File

@ -90,72 +90,120 @@
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<!-- Page Header --> <div>
<div class="d-flex justify-content-between align-items-center mb-4"> <h1 class="h2">
<div> <i class="fas fa-users me-2"></i>Human<span class="fw-light">Resources</span>
<h1 class="h3 mb-1"> </h1>
<i class="fas fa-users me-2"></i>Human Resources Dashboard <p class="text-muted">Manage all HR from one place.</p>
</h1> </div>
<nav aria-label="breadcrumb"> <div class="btn-toolbar mb-2 mb-md-0">
<ol class="breadcrumb mb-0"> <div class="btn-group me-2">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li> <button type="button" class="btn btn-sm btn-outline-secondary" onclick="refreshDashboard()">
<li class="breadcrumb-item active">Human Resources</li>
</ol>
</nav>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary" onclick="refreshDashboard()">
<i class="fas fa-sync-alt me-2"></i>Refresh <i class="fas fa-sync-alt me-2"></i>Refresh
</button> </button>
<div class="btn-group"> <a class="btn btn-sm btn-outline-primary" href="{% url 'hr:employee_create' %}">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown"> <i class="fas fa-user-plus me-2"></i>Add Employee
<i class="fas fa-cog me-2"></i>Quick Actions </a>
</button> <a class="btn btn-sm btn-outline-secondary" href="{% url 'hr:department_create' %}">
<ul class="dropdown-menu"> <i class="fas fa-building me-2"></i>Add Department
<li><a class="dropdown-item" href="{% url 'hr:employee_create' %}"> </a>
<i class="fas fa-user-plus me-2"></i>Add Employee <a class="btn btn-sm btn-outline-theme" href="{% url 'hr:schedule_create' %}">
</a></li> <i class="fas fa-calendar-plus me-2"></i>Create Schedule
<li><a class="dropdown-item" href="{% url 'hr:department_create' %}"> </a>
<i class="fas fa-building me-2"></i>Add Department <a class="btn btn-sm btn-outline-warning" href="#">
</a></li> <i class="fas fa-chart-bar me-2"></i>Generate Report
<li><a class="dropdown-item" href="{% url 'hr:schedule_create' %}"> </a>
<i class="fas fa-calendar-plus me-2"></i>Create Schedule
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">
<i class="fas fa-chart-bar me-2"></i>Generate Report
</a></li>
</ul>
</div>
</div> </div>
</div> </div>
</div>
<div class="container-fluid">
<!-- Statistics Cards --> <!-- Statistics Cards -->
<div class="row mb-4" id="hr-stats"> <div class="row mb-2">
{% include "hr/partials/hr_stats.html" %} <div class="col-lg-2 col-md-4">
<div class="widget widget-stats bg-blue">
<div class="stats-icon stats-icon-lg"><i class="fas fa-users fa-fw"></i></div>
<div class="stats-content">
<div class="stats-title">Total Employees</div>
<div class="stats-number">{{ total_employees }}</div>
<div class="stats-desc">All staff members</div>
</div>
</div>
</div>
<div class="col-lg-2 col-md-4">
<div class="widget widget-stats bg-success">
<div class="stats-icon stats-icon-lg"><i class="fas fa-user-check fa-fw"></i></div>
<div class="stats-content">
<div class="stats-title">Active Employees</div>
<div class="stats-number">{{ active_employees }}</div>
<div class="stats-desc">Currently employed</div>
</div>
</div>
</div>
<div class="col-lg-2 col-md-4">
<div class="widget widget-stats bg-info">
<div class="stats-icon stats-icon-lg"><i class="fas fa-clock fa-fw"></i></div>
<div class="stats-content">
<div class="stats-title">Present Today</div>
<div class="stats-number">{{ employees_clocked_in }}</div>
<div class="stats-desc">Currently employed</div>
</div>
</div>
</div>
<div class="col-lg-2 col-md-4">
<div class="widget widget-stats bg-warning">
<div class="stats-icon stats-icon-lg"><i class="fas fa-building fa-fw"></i></div>
<div class="stats-content">
<div class="stats-title">Departments</div>
<div class="stats-number">{{ departments.count }}</div>
<div class="stats-desc">Active departments</div>
</div>
</div>
</div>
<div class="col-lg-2 col-md-4">
<div class="widget widget-stats bg-red">
<div class="stats-icon stats-icon-lg"><i class="fas fa-star fa-fw"></i></div>
<div class="stats-content">
<div class="stats-title">Pending Reviews</div>
<div class="stats-number">{{ pending_reviews }}</div>
<div class="stats-desc">Awaiting completion</div>
</div>
</div>
</div>
<div class="col-lg-2 col-md-4">
<div class="widget widget-stats bg-black">
<div class="stats-icon stats-icon-lg"><i class="fas fa-stopwatch fa-fw"></i></div>
<div class="stats-content">
<div class="stats-title">Total Hours Today</div>
<div class="stats-number">{{ total_hours_today|floatformat:1 }}h</div>
<div class="stats-desc">Logged work hours</div>
</div>
</div>
</div>
</div> </div>
<div class="row"> <div class="row">
<!-- Recent Activity --> <!-- Recent Activity -->
<div class="col-lg-8"> <div class="col-lg-8">
<div class="card mb-4"> <div class="panel panel-inverse" data-sortable-id="index-1">
<div class="card-header"> <div class="panel-heading">
<div class="d-flex justify-content-between align-items-center"> <h4 class="panel-title">
<h5 class="mb-0"> <i class="fas fa-clock me-2"></i>Recent Activity
<i class="fas fa-clock me-2"></i>Recent Activity </h4>
</h5> <div class="panel-heading-btn">
<div class="btn-group btn-group-sm"> <button type="button" class="btn btn-xs btn-outline-secondary me-2" onclick="refreshActivity()">
<button type="button" class="btn btn-outline-secondary" onclick="refreshActivity()"> <i class="fas fa-sync-alt"></i>
<i class="fas fa-sync-alt"></i> </button>
</button> <a href="#" class="btn btn-xs btn-outline-primary me-2">
<a href="#" class="btn btn-outline-primary"> <i class="fas fa-list me-1"></i>View All
<i class="fas fa-list me-1"></i>View All </a>
</a> <a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
</div> <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> </div>
<div class="card-body"> <div class="panel-body">
<div class="timeline" id="activity-timeline"> <div class="timeline" id="activity-timeline">
<!-- Recent Hires --> <!-- Recent Hires -->
{% for employee in recent_hires %} {% for employee in recent_hires %}
@ -217,13 +265,19 @@
</div> </div>
<!-- Attendance Summary --> <!-- Attendance Summary -->
<div class="card"> <div class="panel panel-inverse" data-sortable-id="index-2">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-clock me-2"></i>Today's Attendance <i class="fas fa-clock me-2"></i>Today's Attendance
</h5> </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>
<div class="card-body"> <div class="panel-body">
{% include "hr/partials/attendance_summary.html" %} {% include "hr/partials/attendance_summary.html" %}
</div> </div>
</div> </div>
@ -232,13 +286,19 @@
<!-- Sidebar --> <!-- Sidebar -->
<div class="col-lg-4"> <div class="col-lg-4">
<!-- Quick Actions --> <!-- Quick Actions -->
<div class="card mb-4"> <div class="panel panel-inverse" data-sortable-id="index-3">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-bolt me-2"></i>Quick Actions <i class="fas fa-bolt me-2"></i>Quick Actions
</h5> </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>
<div class="card-body"> <div class="panel-body">
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<a href="{% url 'hr:employee_create' %}" class="btn btn-primary"> <a href="{% url 'hr:employee_create' %}" class="btn btn-primary">
<i class="fas fa-user-plus me-2"></i>Add Employee <i class="fas fa-user-plus me-2"></i>Add Employee
@ -266,13 +326,22 @@
</div> </div>
<!-- Department Overview --> <!-- Department Overview -->
<div class="card mb-4"> <div class="panel panel-inverse" data-sortable-id="index-4">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-building me-2"></i>Department Overview <i class="fas fa-building me-2"></i>Department Overview
</h5> </h4>
<div class="panel-heading-btn">
<a href="{% url 'hr:department_create' %}" class="btn btn-xs btn-outline-theme me-2">
<i class="fas fa-plus me-1"></i>Add Department
</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>
<div class="card-body"> <div class="panel-body">
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
{% for dept in departments %} {% for dept in departments %}
<div class="list-group-item d-flex justify-content-between align-items-center px-0"> <div class="list-group-item d-flex justify-content-between align-items-center px-0">
@ -286,9 +355,7 @@
<div class="text-center py-3"> <div class="text-center py-3">
<i class="fas fa-building fa-2x text-muted mb-2"></i> <i class="fas fa-building fa-2x text-muted mb-2"></i>
<p class="text-muted mb-0">No departments found</p> <p class="text-muted mb-0">No departments found</p>
<a href="{% url 'hr:department_create' %}" class="btn btn-sm btn-outline-primary mt-2">
<i class="fas fa-plus me-1"></i>Add Department
</a>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@ -296,13 +363,19 @@
</div> </div>
<!-- Pending Tasks --> <!-- Pending Tasks -->
<div class="card"> <div class="panel panel-inverse" data-sortable-id="index-5">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-tasks me-2"></i>Pending Tasks <i class="fas fa-tasks me-2"></i>Pending Tasks
</h5> </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>
<div class="card-body"> <div class="panel-body">
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
{% if pending_reviews > 0 %} {% if pending_reviews > 0 %}
<a href="{% url 'hr:performance_review_list' %}?status=PENDING" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center px-0"> <a href="{% url 'hr:performance_review_list' %}?status=PENDING" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center px-0">
@ -383,51 +456,51 @@ function refreshActivity() {
} }
// Clock in/out functionality // Clock in/out functionality
function clockIn() { {#function clockIn() {#}
fetch('{% url "hr:clock_in" %}', { {# fetch('{% url "hr:clock_in" employee_id%}', {#}
method: 'POST', {# method: 'POST',#}
headers: { {# headers: {#}
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value, {# 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,#}
'Content-Type': 'application/json', {# 'Content-Type': 'application/json',#}
}, {# },#}
}) {# })#}
.then(response => response.json()) {# .then(response => response.json())#}
.then(data => { {# .then(data => {#}
if (data.success) { {# if (data.success) {#}
alert('Clocked in successfully!'); {# alert('Clocked in successfully!');#}
refreshDashboard(); {# refreshDashboard();#}
} else { {# } else {#}
alert('Error clocking in: ' + data.message); {# alert('Error clocking in: ' + data.message);#}
} {# }#}
}) {# })#}
.catch(error => { {# .catch(error => {#}
console.error('Error:', error); {# console.error('Error:', error);#}
alert('Error clocking in'); {# alert('Error clocking in');#}
}); {# });#}
} {# }#}
{##}
function clockOut() { {#function clockOut() {#}
fetch('{% url "hr:clock_out" %}', { {# fetch('{% url "hr:clock_out" %}', {#}
method: 'POST', {# method: 'POST',#}
headers: { {# headers: {#}
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value, {# 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,#}
'Content-Type': 'application/json', {# 'Content-Type': 'application/json',#}
}, {# },#}
}) {# })#}
.then(response => response.json()) {# .then(response => response.json())#}
.then(data => { {# .then(data => {#}
if (data.success) { {# if (data.success) {#}
alert('Clocked out successfully!'); {# alert('Clocked out successfully!');#}
refreshDashboard(); {# refreshDashboard();#}
} else { {# } else {#}
alert('Error clocking out: ' + data.message); {# alert('Error clocking out: ' + data.message);#}
} {# }#}
}) {# })#}
.catch(error => { {# .catch(error => {#}
console.error('Error:', error); {# console.error('Error:', error);#}
alert('Error clocking out'); {# alert('Error clocking out');#}
}); {# });#}
} {# }#}
</script> </script>
{% endblock %} {% endblock %}

View File

@ -28,235 +28,240 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<!-- begin breadcrumb -->
<ol class="breadcrumb float-xl-end">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'hr:dashboard' %}">HR</a></li>
<li class="breadcrumb-item active">Departments</li>
</ol>
<!-- end breadcrumb -->
<!-- begin page-header --> <!-- begin page-header -->
<h1 class="page-header">Department Management <small>view and manage hospital departments</small></h1>
<!-- end page-header -->
<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-users me-2"></i>Department<span class="fw-light">Management</span>
</h1>
<p class="text-muted">view and manage hospital departments.</p>
</div>
<div class="btn-toolbar mb-2 mb-md-0">
</div>
</div>
<!-- end page-header -->
<div class="container-fluid">
<!-- begin row --> <!-- begin row -->
<div class="row"> <div class="row">
<!-- begin col-12 --> <!-- begin col-12 -->
<div class="col-xl-12"> <div class="col-xl-12">
<!-- begin panel --> <!-- begin panel -->
<div class="panel panel-inverse"> <div class="panel panel-inverse">
<!-- begin panel-heading --> <!-- begin panel-heading -->
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title">Departments</h4> <h4 class="panel-title">Departments</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="{% url 'hr:department_create' %}" class="btn btn-xs btn-outline-theme me-2">
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a> <i class="fas fa-plus"></i> Add Department
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
</div>
</div>
<!-- end panel-heading -->
<!-- begin panel-body -->
<div class="panel-body">
<div class="d-flex justify-content-between mb-3">
<div>
<a href="{% url 'hr:department_create' %}" class="btn btn-primary">
<i class="fas fa-plus"></i> Add Department
</a> </a>
<div class="btn-group ms-2 view-toggle" role="group"> <a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<button type="button" class="btn btn-outline-secondary active" id="table-view-btn"> <a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<i class="fas fa-table"></i> Table <a href="javascript:" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
</button> <a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
<button type="button" class="btn btn-outline-secondary" id="card-view-btn">
<i class="fas fa-th-large"></i> Cards
</button>
</div>
</div> </div>
<div class="d-flex"> </div>
<form method="get" class="me-2"> <!-- end panel-heading -->
<div class="input-group">
<input type="text" class="form-control" placeholder="Search departments..." name="search" value="{{ search }}"> <!-- begin panel-body -->
<button class="btn btn-outline-secondary" type="submit"> <div class="panel-body">
<i class="fas fa-search"></i> <div class="d-flex justify-content-between mb-3">
<div>
<div class="btn-group ms-2 view-toggle" role="group">
<button type="button" class="btn btn-outline-secondary active" id="table-view-btn">
<i class="fas fa-table"></i> Table
</button>
<button type="button" class="btn btn-outline-secondary" id="card-view-btn">
<i class="fas fa-th-large"></i> Cards
</button> </button>
</div> </div>
</form> </div>
<div class="dropdown"> <div class="d-flex">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="exportDropdown" data-bs-toggle="dropdown" aria-expanded="false"> <form method="get" class="me-2">
<i class="fas fa-download"></i> Export <div class="input-group">
</button> <input type="text" class="form-control" placeholder="Search departments..." name="search" value="{{ search }}">
<ul class="dropdown-menu" aria-labelledby="exportDropdown"> <button class="btn btn-outline-secondary" type="submit">
<li><a class="dropdown-item" href="#" id="export-csv"><i class="fas fa-file-csv"></i> CSV</a></li> <i class="fas fa-search"></i>
<li><a class="dropdown-item" href="#" id="export-excel"><i class="fas fa-file-excel"></i> Excel</a></li> </button>
<li><a class="dropdown-item" href="#" id="export-pdf"><i class="fas fa-file-pdf"></i> PDF</a></li> </div>
</ul> </form>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="exportDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-download"></i> Export
</button>
<ul class="dropdown-menu" aria-labelledby="exportDropdown">
<li><a class="dropdown-item" href="#" id="export-csv"><i class="fas fa-file-csv"></i> CSV</a></li>
<li><a class="dropdown-item" href="#" id="export-excel"><i class="fas fa-file-excel"></i> Excel</a></li>
<li><a class="dropdown-item" href="#" id="export-pdf"><i class="fas fa-file-pdf"></i> PDF</a></li>
</ul>
</div>
</div> </div>
</div> </div>
</div>
<!-- Table View -->
<!-- Table View --> <div id="table-view">
<div id="table-view"> <table id="departments-table" class="table table-striped table-bordered align-middle">
<table id="departments-table" class="table table-striped table-bordered align-middle"> <thead>
<thead> <tr>
<tr> <th>Department Name</th>
<th>Department Name</th> <th>Code</th>
<th>Code</th> <th>Type</th>
<th>Type</th> <th>Head</th>
<th>Head</th> <th>Employees</th>
<th>Employees</th> <th>Status</th>
<th>Status</th> <th>Actions</th>
<th>Actions</th> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> {% for department in departments %}
{% for department in departments %} <tr>
<tr> <td>
<td> <strong>{{ department.name }}</strong>
<strong>{{ department.name }}</strong> {% if department.parent_department %}
{% if department.parent_department %} <small class="d-block text-muted">
<small class="d-block text-muted"> <i class="fas fa-level-up-alt fa-flip-horizontal"></i> {{ department.parent_department.name }}
<i class="fas fa-level-up-alt fa-flip-horizontal"></i> {{ department.parent_department.name }} </small>
</small> {% endif %}
{% endif %} </td>
</td> <td>{{ department.department_code }}</td>
<td>{{ department.department_code }}</td> <td>{{ department.get_department_type_display }}</td>
<td>{{ department.get_department_type_display }}</td> <td>
<td> {% if department.department_head %}
{% if department.department_head %} <a href="{% url 'hr:employee_detail' department.department_head.id %}">
<a href="{% url 'hr:employee_detail' department.department_head.id %}"> {{ department.department_head.get_full_name }}
{{ department.department_head.get_full_name }} </a>
</a> {% else %}
{% else %} <span class="text-muted">Not assigned</span>
<span class="text-muted">Not assigned</span> {% endif %}
{% endif %} </td>
</td> <td>
<td> <span class="badge bg-primary">{{ department.employee_count }}</span>
<span class="badge bg-primary">{{ department.employee_count }}</span> </td>
</td> <td>
<td> {% if department.is_active %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-danger">Inactive</span>
{% endif %}
</td>
<td>
<div class="btn-group">
<a href="{% url 'hr:department_detail' department.id %}" class="btn btn-sm btn-info">
<i class="fas fa-eye"></i>
</a>
<a href="{% url 'hr:department_update' department.id %}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'hr:department_delete' department.id %}" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i>
</a>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center">No departments found.</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Pagination -->
{% if is_paginated %}
{% include 'partial/pagination.html'%}
{% endif %}
</div>
<!-- Card View -->
<div id="card-view" class="row g-3" style="display: none;">
{% for department in departments %}
<div class="col-md-6 col-lg-4">
<div class="card department-card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">{{ department.name }}</h5>
{% if department.is_active %} {% if department.is_active %}
<span class="badge bg-success">Active</span> <span class="badge bg-success">Active</span>
{% else %} {% else %}
<span class="badge bg-danger">Inactive</span> <span class="badge bg-danger">Inactive</span>
{% endif %} {% endif %}
</td> </div>
<td> <div class="card-body">
<div class="btn-group"> <div class="mb-3">
<strong>Code:</strong> {{ department.department_code }}<br>
<strong>Type:</strong> {{ department.get_department_type_display }}
</div>
<div class="mb-3">
<strong>Department Head:</strong><br>
{% if department.department_head %}
<a href="{% url 'hr:employee_detail' department.department_head.id %}">
{{ department.department_head.get_full_name }}
</a>
{% else %}
<span class="text-muted">Not assigned</span>
{% endif %}
</div>
<div class="mb-3">
<strong>Parent Department:</strong><br>
{% if department.parent_department %}
<a href="{% url 'hr:department_detail' department.parent_department.id %}">
{{ department.parent_department.name }}
</a>
{% else %}
<span class="text-muted">None (Top Level)</span>
{% endif %}
</div>
<div class="department-stats d-flex justify-content-between">
<div>
<i class="fas fa-users"></i> {{ department.employee_count }} Employees
</div>
{% if department.location %}
<div>
<i class="fas fa-map-marker-alt"></i> {{ department.location }}
</div>
{% endif %}
</div>
</div>
<div class="card-footer">
<div class="btn-group w-100">
<a href="{% url 'hr:department_detail' department.id %}" class="btn btn-sm btn-info"> <a href="{% url 'hr:department_detail' department.id %}" class="btn btn-sm btn-info">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i> View
</a> </a>
<a href="{% url 'hr:department_update' department.id %}" class="btn btn-sm btn-primary"> <a href="{% url 'hr:department_update' department.id %}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i> Edit
</a> </a>
<a href="{% url 'hr:department_delete' department.id %}" class="btn btn-sm btn-danger"> <a href="{% url 'hr:department_delete' department.id %}" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i> Delete
</a> </a>
</div> </div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center">No departments found.</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Pagination -->
{% if is_paginated %}
{% include 'partial/pagination.html'%}
{% endif %}
</div>
<!-- Card View -->
<div id="card-view" class="row g-3" style="display: none;">
{% for department in departments %}
<div class="col-md-6 col-lg-4">
<div class="card department-card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">{{ department.name }}</h5>
{% if department.is_active %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-danger">Inactive</span>
{% endif %}
</div>
<div class="card-body">
<div class="mb-3">
<strong>Code:</strong> {{ department.department_code }}<br>
<strong>Type:</strong> {{ department.get_department_type_display }}
</div>
<div class="mb-3">
<strong>Department Head:</strong><br>
{% if department.department_head %}
<a href="{% url 'hr:employee_detail' department.department_head.id %}">
{{ department.department_head.get_full_name }}
</a>
{% else %}
<span class="text-muted">Not assigned</span>
{% endif %}
</div>
<div class="mb-3">
<strong>Parent Department:</strong><br>
{% if department.parent_department %}
<a href="{% url 'hr:department_detail' department.parent_department.id %}">
{{ department.parent_department.name }}
</a>
{% else %}
<span class="text-muted">None (Top Level)</span>
{% endif %}
</div>
<div class="department-stats d-flex justify-content-between">
<div>
<i class="fas fa-users"></i> {{ department.employee_count }} Employees
</div>
{% if department.location %}
<div>
<i class="fas fa-map-marker-alt"></i> {{ department.location }}
</div>
{% endif %}
</div>
</div>
<div class="card-footer">
<div class="btn-group w-100">
<a href="{% url 'hr:department_detail' department.id %}" class="btn btn-sm btn-info">
<i class="fas fa-eye"></i> View
</a>
<a href="{% url 'hr:department_update' department.id %}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i> Edit
</a>
<a href="{% url 'hr:department_delete' department.id %}" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i> Delete
</a>
</div> </div>
</div> </div>
</div> </div>
</div> {% empty %}
{% empty %} <div class="col-12">
<div class="col-12"> <div class="alert alert-info">
<div class="alert alert-info"> No departments found.
No departments found. </div>
</div> </div>
{% endfor %}
<!-- Pagination for Card View -->
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
</div> </div>
{% endfor %}
<!-- Pagination for Card View -->
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
</div> </div>
<!-- end panel-body -->
</div> </div>
<!-- end panel-body --> <!-- end panel -->
</div> </div>
<!-- end panel --> <!-- end col-12 -->
</div> </div>
<!-- end col-12 -->
</div>
<!-- end row --> <!-- end row -->
</div>
{% endblock %} {% endblock %}
{% block js %} {% block js %}

View File

@ -11,13 +11,14 @@
<h1 class="h3 mb-1"> <h1 class="h3 mb-1">
<i class="fas fa-user me-2"></i>{{ employee.get_full_name }} <i class="fas fa-user me-2"></i>{{ employee.get_full_name }}
</h1> </h1>
<nav aria-label="breadcrumb"> <div
<ol class="breadcrumb mb-0"> id="clock-controls-{{ employee.id }}"
<li class="breadcrumb-item"><a href="{% url 'hr:dashboard' %}">HR</a></li> hx-get="{% url 'hr:clock_controls' employee.id %}"
<li class="breadcrumb-item"><a href="{% url 'hr:employee_list' %}">Employees</a></li> hx-trigger="load"
<li class="breadcrumb-item active">{{ employee.get_full_name }}</li> hx-swap="outerHTML">
</ol> <!-- Optional skeleton while loading -->
</nav> <button class="btn btn-outline-secondary" disabled>Loading…</button>
</div>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<a href="{% url 'hr:employee_list' %}" class="btn btn-outline-secondary"> <a href="{% url 'hr:employee_list' %}" class="btn btn-outline-secondary">
@ -60,15 +61,16 @@
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"><i class="fa fa-user-alt me-2 "></i>Employee Details</h4> <h4 class="panel-title"><i class="fa fa-user-alt me-2 "></i>Employee Details</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>
<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> </div>
<div class="panel-body"> <div class="panel-body">
<div class="card mb-4"> <div class="card border border-primary mb-4">
<div class="card-header"> <div class="card-header bg-gradient bg-primary text-white">
<h5 class="mb-0"> <h5 class="mb-0">
<i class="fas fa-id-card me-2"></i>Employee Information <i class="fas fa-id-card me-2"></i>Employee Information
</h5> </h5>
@ -135,8 +137,8 @@
</div> </div>
<!-- Contact Information --> <!-- Contact Information -->
<div class="card mb-4"> <div class="card border border-success mb-4">
<div class="card-header"> <div class="card-header bg-gradient bg-success text-white">
<h5 class="mb-0"> <h5 class="mb-0">
<i class="fas fa-address-book me-2"></i>Contact Information <i class="fas fa-address-book me-2"></i>Contact Information
</h5> </h5>
@ -195,9 +197,9 @@
</div> </div>
<!-- Emergency Contact --> <!-- Emergency Contact -->
{% if employee.emergency_contact_name %}
<div class="card mb-4"> <div class="card border border-danger mb-4">
<div class="card-header"> <div class="card-header bg-gradient bg-danger text-white">
<h5 class="mb-0"> <h5 class="mb-0">
<i class="fas fa-exclamation-triangle me-2"></i>Emergency Contact <i class="fas fa-exclamation-triangle me-2"></i>Emergency Contact
</h5> </h5>
@ -228,11 +230,11 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %}
<!-- Recent Activity --> <!-- Recent Activity -->
<div class="card"> <div class="card border border-warning mb-4">
<div class="card-header"> <div class="card-header bg-gradient bg-warning">
<h5 class="mb-0"> <h5 class="mb-0">
<i class="fas fa-history me-2"></i>Recent Activity <i class="fas fa-history me-2"></i>Recent Activity
</h5> </h5>
@ -285,13 +287,20 @@
</div> </div>
<!-- Quick Stats --> <!-- Quick Stats -->
<div class="card mb-4"> <div class="panel panel-inverse">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-chart-bar me-2"></i>Quick Stats <i class="fas fa-chart-bar me-2"></i>Quick Stats
</h5> </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="card-body"> </div>
<div class="panel-body">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<span class="text-muted">Years of Service</span> <span class="text-muted">Years of Service</span>
<span class="badge bg-primary">{{ employee.years_of_service|floatformat:1 }}</span> <span class="badge bg-primary">{{ employee.years_of_service|floatformat:1 }}</span>
@ -312,14 +321,31 @@
</div> </div>
<!-- Quick Actions --> <!-- Quick Actions -->
<div class="card"> <div class="panel panel-inverse">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-bolt me-2"></i>Quick Actions <i class="fas fa-bolt me-2"></i>Quick Actions
</h5> </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="card-body"> </div>
<div class="panel-body">
<div class="d-grid gap-2"> <div class="d-grid gap-2">
{# <a hx-post="{% url 'hr:clock_in' employee.id %}"#}
{# hx-target="#clock-controls-{{ employee.id }}"#}
{# hx-swap="outerHTML"#}
{# hx-headers='{"X-CSRFToken":"{{ csrf_token }}"}'#}
{# class="btn btn-outline-green">#}
{# <i class="fa fa-check-circle text-green"></i> Clock In#}
{# </a>#}
{# If you don't have open_entry_for_today, pass None and the partial will show "Clock In". #}
<a href="#" class="btn btn-outline-primary"> <a href="#" class="btn btn-outline-primary">
<i class="fas fa-clock me-2"></i>View Schedule <i class="fas fa-clock me-2"></i>View Schedule
</a> </a>

View File

@ -74,7 +74,6 @@
{{ form.father_name.errors.0 }} {{ form.father_name.errors.0 }}
</div> </div>
{% endif %} {% endif %}
<div class="form-text">Name the employee prefers to be called</div>
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">

View File

@ -4,112 +4,54 @@
{% block title %}Employee Management - {{ block.super }}{% endblock %} {% block title %}Employee Management - {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<!-- Page Header --> <div>
<div class="d-flex justify-content-between align-items-center mb-4"> <h1 class="h2">
<div> <i class="fas fa-users me-2"></i>Employee<span class="fw-light">Management</span>
<h1 class="h3 mb-1"> </h1>
<i class="fas fa-users me-2"></i>Employee Management <p class="text-muted">Review employees and add new ones.</p>
</h1> </div>
<nav aria-label="breadcrumb"> <div class="btn-toolbar mb-2 mb-md-0">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'hr:dashboard' %}">HR</a></li>
<li class="breadcrumb-item active">Employees</li>
</ol>
</nav>
</div>
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown"> <a href="{% url 'hr:employee_create' %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-download me-2"></i>Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportEmployees('csv')">
<i class="fas fa-file-csv me-2"></i>Export as CSV
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportEmployees('excel')">
<i class="fas fa-file-excel me-2"></i>Export as Excel
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportEmployees('pdf')">
<i class="fas fa-file-pdf me-2"></i>Export as PDF
</a></li>
</ul>
<a href="{% url 'hr:employee_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Add Employee <i class="fas fa-plus me-2"></i>Add Employee
</a> </a>
</div> <div class="btn-group">
</div> <button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-download me-2"></i>Export
<!-- Statistics Cards --> </button>
<div class="row mb-4"> <ul class="dropdown-menu">
<div class="col-lg-3 col-md-6 mb-3"> <li><a class="dropdown-item" href="#" onclick="exportEmployees('csv')">
<div class="card border-0 shadow-sm h-100"> <i class="fas fa-file-csv me-2"></i>Export as CSV
<div class="card-body"> </a></li>
<div class="d-flex justify-content-between align-items-center"> <li><a class="dropdown-item" href="#" onclick="exportEmployees('excel')">
<div> <i class="fas fa-file-excel me-2"></i>Export as Excel
<h6 class="card-title text-muted mb-1">Total Employees</h6> </a></li>
<h3 class="mb-0 text-primary">{{ total_employees }}</h3> <li><a class="dropdown-item" href="#" onclick="exportEmployees('pdf')">
<small class="text-muted">All staff members</small> <i class="fas fa-file-pdf me-2"></i>Export as PDF
</div> </a></li>
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;"> </ul>
<i class="fas fa-users fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Active Employees</h6>
<h3 class="mb-0 text-success">{{ active_employees }}</h3>
<small class="text-muted">Currently employed</small>
</div>
<div class="bg-success bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-user-check fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">New Hires</h6>
<h3 class="mb-0 text-info">{{ new_hires_count }}</h3>
<small class="text-muted">This month</small>
</div>
<div class="bg-info bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-user-plus fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Departments</h6>
<h3 class="mb-0 text-warning">{{ departments_count }}</h3>
<small class="text-muted">Active departments</small>
</div>
<div class="bg-warning bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-building fa-lg text-white"></i>
</div>
</div>
</div>
</div> </div>
</div> </div>
</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-users-gear me-2"></i>Employee 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 Card --> <!-- Filters Card -->
<div class="card mb-4"> <div class="card border border-success mb-4">
<div class="card-header"> <div class="card-header bg-success text-white">
<h5 class="mb-0"> <h5 class="mb-0">
<i class="fas fa-filter me-2"></i>Filters <i class="fas fa-filter me-2"></i>Filters
</h5> </h5>
@ -165,8 +107,8 @@
</div> </div>
<!-- Employees List --> <!-- Employees List -->
<div class="card"> <div class="card border border-primary">
<div class="card-header"> <div class="card-header bg-primary text-white">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0"> <h5 class="mb-0">
<i class="fas fa-list me-2"></i>Employees <i class="fas fa-list me-2"></i>Employees
@ -175,10 +117,10 @@
{% endif %} {% endif %}
</h5> </h5>
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary" onclick="refreshEmployees()"> <button type="button" class="btn btn-sm btn-outline-light" onclick="refreshEmployees()">
<i class="fas fa-sync-alt me-1"></i>Refresh <i class="fas fa-sync-alt me-1"></i>Refresh
</button> </button>
<button type="button" class="btn btn-outline-secondary" onclick="toggleBulkActions()"> <button type="button" class="btn btn-sm btn-outline-light" onclick="toggleBulkActions()">
<i class="fas fa-check-square me-1"></i>Bulk Actions <i class="fas fa-check-square me-1"></i>Bulk Actions
</button> </button>
</div> </div>
@ -247,7 +189,7 @@
</div> </div>
</td> </td>
<td> <td>
<span class="font-monospace">{{ employee.employee_number }}</span> <span class="fw-bold">{{ employee.employee_id }}</span>
</td> </td>
<td> <td>
{% if employee.department %} {% if employee.department %}
@ -326,7 +268,8 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
</div>
<script> <script>
// Employee list functionality // Employee list functionality
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {

View File

@ -0,0 +1,45 @@
{% load tz %}
<div id="clock-controls-{{ employee.id }}">
{% if time_entry %}
{% if not time_entry.clock_out_time %}
<!-- State: clocked in, waiting to clock out -->
<span class="badge bg-success me-2 py-2 px-3">
<i class="fa fa-check-circle me-1"></i>
Clocked in at {{ time_entry.clock_in_time|localtime|date:"Y-m-d H:i" }}
</span>
<form
hx-post="{% url 'hr:clock_out' employee.id %}"
hx-target="#clock-controls-{{ employee.id }}"
hx-swap="outerHTML"
class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger">
<i class="fa fa-sign-out-alt me-1"></i> Clock Out
</button>
</form>
{% else %}
<!-- State: finished -->
<div class="note alert-success py-2 px-3 mb-0 d-inline-block">
<i class="fa fa-clock me-1"></i>
Clocked in: {{ time_entry.clock_in_time|localtime|date:"Y-m-d H:i" }}
&nbsp;&nbsp;
Clocked out: {{ time_entry.clock_out_time|localtime|date:"Y-m-d H:i" }}
{% if time_entry.total_hours %}
&nbsp;(<strong>{{ time_entry.total_hours|floatformat:2 }}</strong> h)
{% endif %}
</div>
{% endif %}
{% else %}
<!-- State: not clocked in yet -->
<form
hx-post="{% url 'hr:clock_in' employee.id %}"
hx-target="#clock-controls-{{ employee.id }}"
hx-swap="outerHTML"
class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-green">
<i class="fa fa-check-circle me-1"></i> Clock In
</button>
</form>
{% endif %}
</div>

View File

@ -1,102 +1,2 @@
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Total Employees</h6>
<h3 class="mb-0 text-primary">{{ total_employees }}</h3>
<small class="text-muted">All staff members</small>
</div>
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-users fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Active Employees</h6>
<h3 class="mb-0 text-success">{{ active_employees }}</h3>
<small class="text-muted">Currently employed</small>
</div>
<div class="bg-success bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-user-check fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Present Today</h6>
<h3 class="mb-0 text-info">{{ employees_clocked_in }}</h3>
<small class="text-muted">Currently working</small>
</div>
<div class="bg-info bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-clock fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Departments</h6>
<h3 class="mb-0 text-warning">{{ total_departments }}</h3>
<small class="text-muted">Active departments</small>
</div>
<div class="bg-warning bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-building fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Pending Reviews</h6>
<h3 class="mb-0 text-danger">{{ pending_reviews }}</h3>
<small class="text-muted">Awaiting completion</small>
</div>
<div class="bg-danger bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-star fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Total Hours Today</h6>
<h3 class="mb-0 text-secondary">{{ total_hours_today|floatformat:1 }}h</h3>
<small class="text-muted">Logged work hours</small>
</div>
<div class="bg-secondary bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-stopwatch fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>

View File

@ -268,9 +268,9 @@
</div> </div>
</div> </div>
</td> </td>
<td>{{ entry.date|date:"M d, Y" }}</td> <td>{{ entry.work_date|date:"M d, Y" }}</td>
<td>{{ entry.start_time|time:"H:i" }} - {{ entry.end_time|time:"H:i" }}</td> <td>{{ entry.clock_in_time|time:"H:i" }} - {{ entry.clock_out_time|time:"H:i" }}</td>
<td>{{ entry.hours }} hrs</td> <td>{{ entry.regular_hours }} hrs</td>
<td> <td>
{% if entry.is_overtime %} {% if entry.is_overtime %}
<span class="badge bg-danger">Overtime</span> <span class="badge bg-danger">Overtime</span>

View File

@ -94,8 +94,9 @@ urlpatterns = [
# ============================================================================ # ============================================================================
# ACTION URLS FOR WORKFLOW OPERATIONS # ACTION URLS FOR WORKFLOW OPERATIONS
# ============================================================================ # ============================================================================
path('clock-in/', views.clock_in, name='clock_in'), path('employees/<int:employee_id>/clock-controls/', views.clock_controls, name='clock_controls'),
path('clock-out/', views.clock_out, name='clock_out'), path('employees/<int:employee_id>/clock-in/', views.clock_in, name='clock_in'),
path('employees/<int:employee_id>/clock-out/', views.clock_out, name='clock_out'),
path('time-entries/<int:entry_id>/approve/', views.approve_time_entry, name='approve_time_entry'), path('time-entries/<int:entry_id>/approve/', views.approve_time_entry, name='approve_time_entry'),
path('schedules/<int:schedule_id>/publish/', views.publish_schedule, name='publish_schedule'), path('schedules/<int:schedule_id>/publish/', views.publish_schedule, name='publish_schedule'),

View File

@ -4,6 +4,8 @@ HR app views with comprehensive CRUD operations following healthcare best practi
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST, require_GET
from django.template.loader import render_to_string
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages from django.contrib import messages
from django.views.generic import ( from django.views.generic import (
@ -29,6 +31,7 @@ from .forms import (
from core.utils import AuditLogger from core.utils import AuditLogger
class HRDashboardView(LoginRequiredMixin, TemplateView): class HRDashboardView(LoginRequiredMixin, TemplateView):
""" """
Main HR dashboard with comprehensive statistics and recent activity. Main HR dashboard with comprehensive statistics and recent activity.
@ -45,7 +48,7 @@ class HRDashboardView(LoginRequiredMixin, TemplateView):
tenant=self.request.user.tenant, tenant=self.request.user.tenant,
employment_status='ACTIVE' employment_status='ACTIVE'
).count(), ).count(),
'total_departments': Department.objects.filter(tenant=self.request.user.tenant).count(), 'departments': Department.objects.filter(tenant=self.request.user.tenant),
'pending_reviews': PerformanceReview.objects.filter( 'pending_reviews': PerformanceReview.objects.filter(
employee__tenant=self.request.user.tenant, employee__tenant=self.request.user.tenant,
status='PENDING' status='PENDING'
@ -1098,103 +1101,193 @@ def attendance_summary(request):
return render(request, 'hr/partials/attendance_summary.html', context) return render(request, 'hr/partials/attendance_summary.html', context)
@login_required
def clock_in(request): @require_GET
""" def clock_controls(request, employee_id):
Clock in an employee. """Return the clock controls partial for today's state (HTMX GET)."""
""" employee = get_object_or_404(Employee, id=employee_id, tenant=request.user.tenant)
if request.method == 'POST': today = timezone.localdate()
employee_id = request.POST.get('employee_id')
# Prefer the open entry for today; otherwise the last entry today (finished)
try: time_entry = (
employee = Employee.objects.get( TimeEntry.objects
id=employee_id, .filter(employee=employee, work_date=today)
tenant=request.user.tenant .order_by('clock_out_time', '-clock_in_time') # open first (clock_out_time NULL), else latest finished
) .first()
)
# Check if already clocked in today
today = timezone.now().date() return render(request, 'hr/partials/clock_controls.html', {
existing_entry = TimeEntry.objects.filter( 'employee': employee,
employee=employee, 'time_entry': time_entry,
work_date=today, })
clock_out_time__isnull=True
).first()
if existing_entry:
return JsonResponse({
'success': False,
'message': 'Employee is already clocked in.'
})
# Create new time entry
time_entry = TimeEntry.objects.create(
employee=employee,
work_date=today,
clock_in_time=timezone.now(),
status='DRAFT'
)
return JsonResponse({
'success': True,
'message': f'{employee.get_full_name()} clocked in successfully.',
'time_entry_id': time_entry.id
})
except Employee.DoesNotExist:
return JsonResponse({
'success': False,
'message': 'Employee not found.'
})
return JsonResponse({'success': False, 'message': 'Invalid request.'})
@login_required def _render_controls(request, employee, time_entry):
def clock_out(request): html = render_to_string('hr/partials/clock_controls.html',
""" {'employee': employee, 'time_entry': time_entry},
Clock out an employee. request=request)
""" return HttpResponse(html)
if request.method == 'POST':
employee_id = request.POST.get('employee_id') @require_POST
def clock_in(request, employee_id):
try: employee = get_object_or_404(Employee, id=employee_id, tenant=request.user.tenant)
employee = Employee.objects.get( today = timezone.localdate()
id=employee_id,
tenant=request.user.tenant open_entry = TimeEntry.objects.filter(
) employee=employee, work_date=today, clock_out_time__isnull=True
).first()
# Find active time entry if open_entry:
today = timezone.now().date() return _render_controls(request, employee, open_entry)
time_entry = TimeEntry.objects.filter(
employee=employee, time_entry = TimeEntry.objects.create(
work_date=today, employee=employee, work_date=today, clock_in_time=timezone.now(), status='DRAFT'
clock_out_time__isnull=True )
).first()
if request.headers.get('HX-Request'):
if not time_entry: return _render_controls(request, employee, time_entry)
return JsonResponse({ return JsonResponse({'success': True, 'time_entry_id': time_entry.id})
'success': False,
'message': 'No active clock-in found for this employee.' @require_POST
}) def clock_out(request, employee_id):
employee = get_object_or_404(Employee, id=employee_id, tenant=request.user.tenant)
# Update time entry today = timezone.localdate()
time_entry.clock_out_time = timezone.now()
time_entry.save() # This will trigger the save method to calculate hours time_entry = TimeEntry.objects.filter(
employee=employee, work_date=today, clock_out_time__isnull=True
return JsonResponse({ ).first()
'success': True, if not time_entry:
'message': f'{employee.get_full_name()} clocked out successfully.', # Re-render to default state (will show "Clock In")
'time_entry_id': time_entry.id, if request.headers.get('HX-Request'):
'hours_worked': float(time_entry.total_hours) return _render_controls(request, employee, None)
}) return JsonResponse({'success': False, 'message': 'No active clock-in found.'}, status=400)
except Employee.DoesNotExist: time_entry.clock_out_time = timezone.now()
return JsonResponse({ time_entry.save()
'success': False,
'message': 'Employee not found.' if request.headers.get('HX-Request'):
}) return _render_controls(request, employee, time_entry)
return JsonResponse({'success': True, 'time_entry_id': time_entry.id})
return JsonResponse({'success': False, 'message': 'Invalid request.'})
# @login_required
# @require_POST
# def clock_in(request, employee_id):
# """
# Clock in an employee. If HTMX, return the updated controls partial (button -> time).
# Otherwise return JSON.
# """
# # Prefer the URL param; fall back to POST field for safety
# emp_id = employee_id or request.POST.get('employee_id')
#
# try:
# employee = Employee.objects.get(id=emp_id, tenant=request.user.tenant)
# except Employee.DoesNotExist:
# if request.headers.get('HX-Request'):
# return HttpResponse("Employee not found.", status=404)
# return JsonResponse({'success': False, 'message': 'Employee not found.'}, status=404)
#
# today = timezone.now().date()
# open_entry = TimeEntry.objects.filter(
# employee=employee,
# work_date=today,
# clock_out_time__isnull=True
# ).first()
#
# if open_entry:
# # Already clocked in — just re-render the controls so the UI shows the time
# if request.headers.get('HX-Request'):
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': employee, 'time_entry': open_entry},
# request=request
# )
# return HttpResponse(html)
# return JsonResponse({'success': False, 'message': 'Employee is already clocked in.'}, status=400)
#
# # Create new time entry
# time_entry = TimeEntry.objects.create(
# employee=employee,
# work_date=today,
# clock_in_time=timezone.now(),
# status='DRAFT'
# )
#
# if request.headers.get('HX-Request'):
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': employee, 'time_entry': time_entry},
# request=request
# )
# return HttpResponse(html)
#
# return JsonResponse({
# 'success': True,
# 'message': f'{employee.get_full_name()} clocked in successfully.',
# 'time_entry_id': time_entry.id
# })
#
#
# @login_required
# @require_POST
# def clock_out(request, employee_id):
# """
# Clock out an employee.
# If HTMX, return the updated controls partial (button -> times).
# Otherwise, return JSON.
# """
# emp_id = employee_id or request.POST.get('employee_id')
#
# # Find employee in same tenant
# try:
# employee = Employee.objects.get(id=emp_id, tenant=request.user.tenant)
# except Employee.DoesNotExist:
# if request.headers.get('HX-Request'):
# # Re-render controls without an active entry (will show "Clock In")
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': None, 'time_entry': None}, # or pass the real employee if you want the button still visible
# request=request
# )
# return HttpResponse(html, status=404)
# return JsonResponse({'success': False, 'message': 'Employee not found.'}, status=404)
#
# # Locate today's open entry
# today = timezone.now().date()
# time_entry = TimeEntry.objects.filter(
# employee=employee,
# work_date=today,
# clock_out_time__isnull=True
# ).first()
#
# if not time_entry:
# if request.headers.get('HX-Request'):
# # No open entry → re-render to show "Clock In"
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': employee, 'time_entry': None},
# request=request
# )
# return HttpResponse(html, status=400)
# return JsonResponse({'success': False, 'message': 'No active clock-in found for this employee.'}, status=400)
#
# # Close the entry
# time_entry.clock_out_time = timezone.now()
# time_entry.save() # if your model computes total_hours on save
#
# if request.headers.get('HX-Request'):
# html = render_to_string(
# 'hr/partials/clock_controls.html',
# {'employee': employee, 'time_entry': time_entry},
# request=request
# )
# return HttpResponse(html)
#
# return JsonResponse({
# 'success': True,
# 'message': f'{employee.get_full_name()} clocked out successfully.',
# 'time_entry_id': time_entry.id,
# # If total_hours is Decimal, this makes it JSON-safe:
# 'hours_worked': float(getattr(time_entry, 'total_hours', 0) or 0)
# })
@login_required @login_required
@ -2644,111 +2737,111 @@ def assign_department_head(request, pk):
# return redirect('hr:time_entry_detail', pk=time_entry.pk) # return redirect('hr:time_entry_detail', pk=time_entry.pk)
# #
# #
# @login_required @login_required
# def mark_time_entry_paid(request, pk): def mark_time_entry_paid(request, pk):
# """ """
# Mark a time entry as paid. Mark a time entry as paid.
# """ """
# time_entry = get_object_or_404( time_entry = get_object_or_404(
# TimeEntry, TimeEntry,
# pk=pk, pk=pk,
# tenant=request.user.tenant tenant=request.user.tenant
# ) )
#
# if request.method == 'POST': if request.method == 'POST':
# if not request.user.has_perm('hr.change_timeentry'): if not request.user.has_perm('hr.change_timeentry'):
# messages.error(request, "You don't have permission to mark time entries as paid.") messages.error(request, "You don't have permission to mark time entries as paid.")
# return redirect('hr:time_entry_detail', pk=time_entry.pk) return redirect('hr:time_entry_detail', pk=time_entry.pk)
#
# time_entry.is_paid = True time_entry.is_paid = True
# time_entry.payment_date = timezone.now().date() time_entry.payment_date = timezone.now().date()
# time_entry.save() time_entry.save()
#
# messages.success( messages.success(
# request, request,
# f"Time entry for {time_entry.employee.get_full_name()} on {time_entry.entry_date} marked as paid." f"Time entry for {time_entry.employee.get_full_name()} on {time_entry.entry_date} marked as paid."
# ) )
#
# redirect_url = request.POST.get('next', reverse('hr:time_entry_detail', kwargs={'pk': time_entry.pk})) redirect_url = request.POST.get('next', reverse('hr:time_entry_detail', kwargs={'pk': time_entry.pk}))
# return redirect(redirect_url) return redirect(redirect_url)
#
# return redirect('hr:time_entry_detail', pk=time_entry.pk) return redirect('hr:time_entry_detail', pk=time_entry.pk)
#
#
# @login_required @login_required
# def bulk_approve_time_entries(request): def bulk_approve_time_entries(request):
# """ """
# Bulk approve time entries. Bulk approve time entries.
# """ """
# if not request.user.has_perm('hr.approve_timeentry'): if not request.user.has_perm('hr.approve_timeentry'):
# messages.error(request, "You don't have permission to approve time entries.") messages.error(request, "You don't have permission to approve time entries.")
# return redirect('hr:time_entry_list') return redirect('hr:time_entry_list')
#
# if request.method == 'POST': if request.method == 'POST':
# entry_ids = request.POST.getlist('entry_ids') entry_ids = request.POST.getlist('entry_ids')
#
# if not entry_ids: if not entry_ids:
# messages.warning(request, "No time entries selected for approval.") messages.warning(request, "No time entries selected for approval.")
# return redirect('hr:time_entry_list') return redirect('hr:time_entry_list')
#
# # Get entries that belong to this tenant # Get entries that belong to this tenant
# entries = TimeEntry.objects.filter( entries = TimeEntry.objects.filter(
# tenant=request.user.tenant, tenant=request.user.tenant,
# pk__in=entry_ids, pk__in=entry_ids,
# is_approved=False is_approved=False
# ) )
#
# # Update all entries # Update all entries
# updated_count = entries.update( updated_count = entries.update(
# is_approved=True, is_approved=True,
# approved_by=request.user, approved_by=request.user,
# approval_date=timezone.now() approval_date=timezone.now()
# ) )
#
# messages.success(request, f"{updated_count} time entries approved successfully.") messages.success(request, f"{updated_count} time entries approved successfully.")
#
# redirect_url = request.POST.get('next', reverse('hr:time_entry_list')) redirect_url = request.POST.get('next', reverse('hr:time_entry_list'))
# return redirect(redirect_url) return redirect(redirect_url)
#
# return redirect('hr:time_entry_list') return redirect('hr:time_entry_list')
#
#
# @login_required @login_required
# def bulk_mark_time_entries_paid(request): def bulk_mark_time_entries_paid(request):
# """ """
# Bulk mark time entries as paid. Bulk mark time entries as paid.
# """ """
# if not request.user.has_perm('hr.change_timeentry'): if not request.user.has_perm('hr.change_timeentry'):
# messages.error(request, "You don't have permission to mark time entries as paid.") messages.error(request, "You don't have permission to mark time entries as paid.")
# return redirect('hr:time_entry_list') return redirect('hr:time_entry_list')
#
# if request.method == 'POST': if request.method == 'POST':
# entry_ids = request.POST.getlist('entry_ids') entry_ids = request.POST.getlist('entry_ids')
#
# if not entry_ids: if not entry_ids:
# messages.warning(request, "No time entries selected for payment.") messages.warning(request, "No time entries selected for payment.")
# return redirect('hr:time_entry_list') return redirect('hr:time_entry_list')
#
# # Get entries that belong to this tenant # Get entries that belong to this tenant
# entries = TimeEntry.objects.filter( entries = TimeEntry.objects.filter(
# tenant=request.user.tenant, tenant=request.user.tenant,
# pk__in=entry_ids, pk__in=entry_ids,
# is_approved=True, is_approved=True,
# is_paid=False is_paid=False
# ) )
#
# # Update all entries # Update all entries
# updated_count = entries.update( updated_count = entries.update(
# is_paid=True, is_paid=True,
# payment_date=timezone.now().date() payment_date=timezone.now().date()
# ) )
#
# messages.success(request, f"{updated_count} time entries marked as paid successfully.") messages.success(request, f"{updated_count} time entries marked as paid successfully.")
#
# redirect_url = request.POST.get('next', reverse('hr:time_entry_list')) redirect_url = request.POST.get('next', reverse('hr:time_entry_list'))
# return redirect(redirect_url) return redirect(redirect_url)
#
# return redirect('hr:time_entry_list') return redirect('hr:time_entry_list')
# #
# #
# class PerformanceReviewListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): # class PerformanceReviewListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
@ -2950,76 +3043,76 @@ def assign_department_head(request, pk):
# return reverse('hr:performance_review_detail', kwargs={'pk': self.object.pk}) # return reverse('hr:performance_review_detail', kwargs={'pk': self.object.pk})
# #
# #
# @login_required @login_required
# def acknowledge_review(request, pk): def acknowledge_review(request, pk):
# """ """
# Acknowledge a performance review. Acknowledge a performance review.
# """ """
# review = get_object_or_404( review = get_object_or_404(
# PerformanceReview, PerformanceReview,
# pk=pk, pk=pk,
# tenant=request.user.tenant tenant=request.user.tenant
# ) )
#
# # Check if the user is the employee being reviewed # Check if the user is the employee being reviewed
# if hasattr(request.user, 'employee_profile') and request.user.employee_profile == review.employee: if hasattr(request.user, 'employee_profile') and request.user.employee_profile == review.employee:
# if request.method == 'POST': if request.method == 'POST':
# review.is_acknowledged = True review.is_acknowledged = True
# review.acknowledgment_date = timezone.now() review.acknowledgment_date = timezone.now()
# review.save() review.save()
#
# messages.success(request, "You have acknowledged this performance review.") messages.success(request, "You have acknowledged this performance review.")
#
# redirect_url = request.POST.get('next', reverse('hr:performance_review_detail', kwargs={'pk': review.pk})) redirect_url = request.POST.get('next', reverse('hr:performance_review_detail', kwargs={'pk': review.pk}))
# return redirect(redirect_url) return redirect(redirect_url)
# else: else:
# messages.error(request, "Only the employee being reviewed can acknowledge this review.") messages.error(request, "Only the employee being reviewed can acknowledge this review.")
#
# return redirect('hr:performance_review_detail', pk=review.pk) return redirect('hr:performance_review_detail', pk=review.pk)
#
#
# @login_required @login_required
# def complete_review(request, pk): def complete_review(request, pk):
# """ """
# Mark a performance review as completed. Mark a performance review as completed.
# """ """
# review = get_object_or_404( review = get_object_or_404(
# PerformanceReview, PerformanceReview,
# pk=pk, pk=pk,
# tenant=request.user.tenant tenant=request.user.tenant
# ) )
#
# if request.method == 'POST': if request.method == 'POST':
# if not request.user.has_perm('hr.change_performancereview'): if not request.user.has_perm('hr.change_performancereview'):
# messages.error(request, "You don't have permission to complete reviews.") messages.error(request, "You don't have permission to complete reviews.")
# return redirect('hr:performance_review_detail', pk=review.pk) return redirect('hr:performance_review_detail', pk=review.pk)
#
# # Check if reviewer is the one completing the review # Check if reviewer is the one completing the review
# if review.reviewer != request.user: if review.reviewer != request.user:
# messages.warning(request, "Only the assigned reviewer should complete this review.") messages.warning(request, "Only the assigned reviewer should complete this review.")
#
# # Update review status # Update review status
# review.status = 'COMPLETED' review.status = 'COMPLETED'
# review.completion_date = timezone.now().date() review.completion_date = timezone.now().date()
# review.save() review.save()
#
# # Update employee's last and next review dates # Update employee's last and next review dates
# employee = review.employee employee = review.employee
# employee.last_review_date = review.completion_date employee.last_review_date = review.completion_date
# employee.next_review_date = review.next_review_date employee.next_review_date = review.next_review_date
# employee.save() employee.save()
#
# messages.success( messages.success(
# request, request,
# f"Performance review for {review.employee.get_full_name()} marked as completed." f"Performance review for {review.employee.get_full_name()} marked as completed."
# ) )
#
# redirect_url = request.POST.get('next', reverse('hr:performance_review_detail', kwargs={'pk': review.pk})) redirect_url = request.POST.get('next', reverse('hr:performance_review_detail', kwargs={'pk': review.pk}))
# return redirect(redirect_url) return redirect(redirect_url)
#
# return redirect('hr:performance_review_detail', pk=review.pk) return redirect('hr:performance_review_detail', pk=review.pk)
#
#
# class TrainingRecordListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): # class TrainingRecordListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """ # """
# List view for training records. # List view for training records.
@ -3349,36 +3442,36 @@ def assign_department_head(request, pk):
# return redirect('hr:dashboard') # return redirect('hr:dashboard')
# #
# #
# @login_required @login_required
# def employee_schedule(request, employee_id): def employee_schedule(request, employee_id):
# """ """
# View an employee's schedule. View an employee's schedule.
# """ """
# employee = get_object_or_404( employee = get_object_or_404(
# Employee, Employee,
# pk=employee_id, pk=employee_id,
# tenant=request.user.tenant tenant=request.user.tenant
# ) )
#
# # Get current schedule # Get current schedule
# current_schedule = Schedule.objects.filter( current_schedule = Schedule.objects.filter(
# employee=employee, employee=employee,
# is_current=True, is_current=True,
# is_active=True is_active=True
# ).first() ).first()
#
# # Get upcoming assignments # Get upcoming assignments
# today = timezone.now().date() today = timezone.now().date()
# upcoming_assignments = ScheduleAssignment.objects.filter( upcoming_assignments = ScheduleAssignment.objects.filter(
# schedule__employee=employee, schedule__employee=employee,
# assignment_date__gte=today assignment_date__gte=today
# ).order_by('assignment_date', 'start_time')[:14] # Next 2 weeks ).order_by('assignment_date', 'start_time')[:14] # Next 2 weeks
#
# context = { context = {
# 'employee': employee, 'employee': employee,
# 'current_schedule': current_schedule, 'current_schedule': current_schedule,
# 'upcoming_assignments': upcoming_assignments, 'upcoming_assignments': upcoming_assignments,
# } }
#
# return render(request, 'hr/employee_schedule.html', context) return render(request, 'hr/employee_schedule.html', context)
#

View File

@ -114,7 +114,7 @@ class BedAdmin(admin.ModelAdmin):
'fields': ('ward', 'bed_number', 'room_number', 'bed_type', 'room_type') 'fields': ('ward', 'bed_number', 'room_number', 'bed_type', 'room_type')
}), }),
('Current Status', { ('Current Status', {
'fields': ('status', 'current_patient', 'current_admission', 'fields': ('status', 'current_admission',
'occupied_since', 'reserved_until') 'occupied_since', 'reserved_until')
}), }),
('Equipment & Features', { ('Equipment & Features', {
@ -451,6 +451,9 @@ class SurgeryScheduleAdmin(admin.ModelAdmin):
('Basic Information', { ('Basic Information', {
'fields': ('tenant', 'surgery_id', 'patient', 'admission') 'fields': ('tenant', 'surgery_id', 'patient', 'admission')
}), }),
# ('Operating Room',{
# 'fields': ('operating_room')
# }),
('Surgery Details', { ('Surgery Details', {
'fields': ('procedure_name', 'procedure_code', 'surgery_type', 'preop_diagnosis') 'fields': ('procedure_name', 'procedure_code', 'surgery_type', 'preop_diagnosis')
}), }),

View File

@ -42,7 +42,7 @@ class WardForm(forms.ModelForm):
self.fields['nurse_manager'].queryset = User.objects.filter( self.fields['nurse_manager'].queryset = User.objects.filter(
tenant=user.tenant, tenant=user.tenant,
is_active=True, is_active=True,
role__in=['NURSE', 'NURSE_MANAGER'] employee_profile__role__in=['NURSE', 'NURSE_MANAGER']
).order_by('last_name', 'first_name') ).order_by('last_name', 'first_name')
def clean_total_beds(self): def clean_total_beds(self):

View File

@ -1157,6 +1157,7 @@ class Transfer(models.Model):
('COMPLETED', 'Completed'), ('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'), ('CANCELLED', 'Cancelled'),
('DELAYED', 'Delayed'), ('DELAYED', 'Delayed'),
('REJECTED', 'Rejected'),
] ]
PRIORITY_CHOICES = [ PRIORITY_CHOICES = [
('ROUTINE', 'Routine'), ('ROUTINE', 'Routine'),

Binary file not shown.

View File

@ -53,16 +53,21 @@
<tr><td>Type</td><td>{{ admission.get_admission_type_display }}</td></tr> <tr><td>Type</td><td>{{ admission.get_admission_type_display }}</td></tr>
<tr><td>Status</td><td> <tr><td>Status</td><td>
{% if admission.status == 'ADMITTED' %} {% if admission.status == 'ADMITTED' %}
<span class="badge bg-success">{{ admission.get_status_display }}</span> <span class="badge bg-success">
{% elif admission.status == 'PENDING' %} {% elif admission.status == 'PENDING' %}
<span class="badge bg-warning">{{ admission.get_status_display }}</span> <span class="badge bg-warning">
{% elif admission.status == 'TRANSFERRED' %} {% elif admission.status == 'TRANSFERRED' %}
<span class="badge bg-info">{{ admission.get_status_display }}</span> <span class="badge bg-info">
{% elif admission.status == 'DISCHARGED' %} {% elif admission.status == 'DISCHARGED' %}
<span class="badge bg-secondary">{{ admission.get_status_display }}</span> <span class="badge bg-secondary">
{% elif admission.status == 'CANCELLED' %} {% elif admission.status == 'CANCELLED' %}
<span class="badge bg-danger">{{ admission.get_status_display }}</span> <span class="badge bg-danger">
{% elif admission.status == 'LEFT_AMA' %}
<span class="badge bg-purple">
{% elif admission.status == 'DECEASED' %}
<span class="badge bg-black">
{% endif %} {% endif %}
{{ admission.get_status_display }}</span>
</td></tr> </td></tr>
<tr><td>Priority</td><td>{{ admission.get_priority_display }}</td></tr> <tr><td>Priority</td><td>{{ admission.get_priority_display }}</td></tr>
<tr><td>Length of Stay</td><td>{{ admission.length_of_stay }} days</td></tr> <tr><td>Length of Stay</td><td>{{ admission.length_of_stay }} days</td></tr>

View File

@ -4,414 +4,432 @@
{% block title %}Bed {{ object.bed_number }} Details{% endblock %} {% block title %}Bed {{ object.bed_number }} Details{% endblock %}
{% block content %} {% block content %}
<div class="d-flex align-items-center mb-3"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div> <div>
<ol class="breadcrumb"> <h1 class="h2">
<li class="breadcrumb-item"><a href="{% url 'inpatients:dashboard' %}">Inpatients</a></li> <i class="fas fa-bed me-2"></i>Bed<span class="fw-light">Details</span>
<li class="breadcrumb-item"><a href="{% url 'inpatients:bed_management' %}">Bed Management</a></li> </h1>
<li class="breadcrumb-item active">Bed {{ object.bed_number }}</li> <p class="text-muted">{{ object.bed_number }}</p>
</ol>
<h1 class="page-header mb-0">Bed {{ object.bed_number }} Details</h1>
</div> </div>
<div class="ms-auto"> <div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group"> <div class="btn-group me-2">
<a href="{% url 'inpatients:bed_update' object.pk %}" class="btn btn-primary"> <a href="{% url 'inpatients:bed_update' object.pk %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-edit me-2"></i>Edit Bed <i class="fas fa-edit me-2"></i>Edit Bed
</a> </a>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown"> <button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle Dropdown</span> <span class="visually-hidden">Toggle Dropdown</span>
<i class="fas fa-ellipsis-v"></i> <i class="fas fa-ellipsis-v"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('available')"> <li>
<i class="fas fa-check text-success me-2"></i>Mark Available <a class="dropdown-item" href="#" onclick="updateBedStatus('available')">
</a></li> <i class="fas fa-check text-success me-2"></i>Mark Available
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('occupied')"> </a>
<i class="fas fa-user text-warning me-2"></i>Mark Occupied </li>
</a></li> <li>
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('maintenance')"> <a class="dropdown-item" href="#" onclick="updateBedStatus('occupied')">
<i class="fas fa-tools text-info me-2"></i>Mark for Maintenance <i class="fas fa-user text-warning me-2"></i>Mark Occupied
</a></li> </a>
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('cleaning')"> </li>
<i class="fas fa-broom text-primary me-2"></i>Mark for Cleaning <li>
</a></li> <a class="dropdown-item" href="#" onclick="updateBedStatus('maintenance')">
<li><hr class="dropdown-divider"></li> <i class="fas fa-tools text-info me-2"></i>Mark for Maintenance
{# <li><a class="dropdown-item text-danger" href="{% url 'inpatients:bed_confirm_delete' object.pk %}">#} </a>
{# <i class="fas fa-trash me-2"></i>Delete Bed#} </li>
{# </a></li>#} <li>
<a class="dropdown-item" href="#" onclick="updateBedStatus('cleaning')">
<i class="fas fa-broom text-primary me-2"></i>Mark for Cleaning
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
<div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-xl-8"> <div class="col-xl-9">
<!-- Bed Information --> <!-- Bed Information -->
<div class="card mb-4"> <div class="panel panel-inverse" data-sortable-id="index-1">
<div class="card-header"> <div class="panel-heading">
<h4 class="card-title"> <h4 class="panel-title"><i class="fa fa-bed me-2"></i>Bed Information</h4>
<i class="fas fa-bed me-2"></i> <div class="panel-heading-btn">
Bed Information <span class="badge bg-{% if object.status == 'AVAILABLE' %}success{% elif object.status == 'OCCUPIED' %}warning{% elif object.status == 'MAINTENANCE' %}danger{% elif object.status == 'RESERVED' %}purple{% else %}info{% endif %} fs-6 me-2">
</h4> {{ object.get_status_display }}
<div class="card-toolbar"> </span>
<span class="badge bg-{% if object.status == 'available' %}success{% elif object.status == 'occupied' %}warning{% elif object.status == 'maintenance' %}danger{% else %}info{% endif %} fs-6"> <a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
{{ object.get_status_display }} <a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
</span> <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="card-body">
<div class="row">
<div class="col-md-6">
<table class="table table-borderless">
<tr>
<td class="fw-bold">Bed Number:</td>
<td>{{ object.bed_number }}</td>
</tr>
<tr>
<td class="fw-bold">Ward:</td>
<td>
<a href="{% url 'inpatients:ward_detail' object.ward.pk %}">
{{ object.ward.name }}
</a>
</td>
</tr>
<tr>
<td class="fw-bold">Room:</td>
<td>{{ object.room_number|default:"Not assigned" }}</td>
</tr>
<tr>
<td class="fw-bold">Bed Type:</td>
<td>{{ object.get_bed_type_display }}</td>
</tr>
<tr>
<td class="fw-bold">Status:</td>
<td>
<span class="badge bg-{% if object.status == 'available' %}success{% elif object.status == 'occupied' %}warning{% elif object.status == 'maintenance' %}danger{% else %}info{% endif %}">
{{ object.get_status_display }}
</span>
</td>
</tr>
</table>
</div>
<div class="col-md-6">
<table class="table table-borderless">
<tr>
<td class="fw-bold">Features:</td>
<td>
{% if object.has_oxygen %}
<span class="badge bg-info me-1">Oxygen</span>
{% endif %}
{% if object.has_suction %}
<span class="badge bg-info me-1">Suction</span>
{% endif %}
{% if object.has_monitor %}
<span class="badge bg-info me-1">Monitor</span>
{% endif %}
{% if object.is_isolation %}
<span class="badge bg-warning me-1">Isolation</span>
{% endif %}
{% if not object.has_oxygen and not object.has_suction and not object.has_monitor and not object.is_isolation %}
<span class="text-muted">Standard bed</span>
{% endif %}
</td>
</tr>
<tr>
<td class="fw-bold">Last Cleaned:</td>
<td>{{ object.last_cleaned|date:"M d, Y g:i A"|default:"Not recorded" }}</td>
</tr>
<tr>
<td class="fw-bold">Last Maintenance:</td>
<td>{{ object.last_maintenance|date:"M d, Y g:i A"|default:"Not recorded" }}</td>
</tr>
<tr>
<td class="fw-bold">Created:</td>
<td>{{ object.created_at|date:"M d, Y g:i A" }}</td>
</tr>
<tr>
<td class="fw-bold">Last Updated:</td>
<td>{{ object.updated_at|date:"M d, Y g:i A" }}</td>
</tr>
</table>
</div> </div>
</div> </div>
<div class="panel-body">
{% if object.notes %} <div class="row">
<div class="mt-3"> <div class="col-md-6">
<h6>Notes:</h6> <table class="table table-borderless">
<div class="alert alert-info">
{{ object.notes }}
</div>
</div>
{% endif %}
</div>
</div>
<!-- Current Patient -->
{% if object.current_patient %}
<div class="card mb-4">
<div class="card-header">
<h4 class="card-title">
<i class="fas fa-user-injured me-2"></i>
Current Patient
</h4>
</div>
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<div class="avatar avatar-lg">
{% if object.current_patient.profile_picture %}
<img src="{{ object.current_patient.profile_picture.url }}" alt="{{ object.current_patient.get_full_name }}" class="rounded-circle">
{% else %}
<div class="avatar-initial rounded-circle bg-primary">
{{ object.current_patient.first_name|first }}{{ object.current_patient.last_name|first }}
</div>
{% endif %}
</div>
</div>
<div class="flex-grow-1 ms-3">
<h5 class="mb-1">
<a href="{% url 'patients:patient_detail' object.current_patient.pk %}">
{{ object.current_patient.get_full_name }}
</a>
</h5>
<div class="text-muted mb-1">
<strong>MRN:</strong> {{ object.current_patient.medical_record_number }}
</div>
<div class="text-muted mb-1">
<strong>DOB:</strong> {{ object.current_patient.date_of_birth|date:"M d, Y" }}
</div>
<div class="text-muted">
<strong>Admitted:</strong> {{ object.current_admission.admission_date|date:"M d, Y g:i A" }}
</div>
</div>
<div class="flex-shrink-0">
<a href="{% url 'inpatients:admission_detail' object.current_admission.pk %}" class="btn btn-outline-primary">
<i class="fas fa-eye me-2"></i>View Admission
</a>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Bed History -->
<div class="card">
<div class="card-header">
<h4 class="card-title">
<i class="fas fa-history me-2"></i>
Bed History
</h4>
<div class="card-toolbar">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="refreshHistory()">
<i class="fas fa-sync me-2"></i>Refresh
</button>
</div>
</div>
<div class="card-body">
{% if bed_history %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr> <tr>
<th>Date</th> <td class="fw-bold">Bed Number:</td>
<th>Action</th> <td>{{ object.bed_number }}</td>
<th>Patient</th>
<th>Duration</th>
<th>Notes</th>
</tr> </tr>
</thead>
<tbody>
{% for history in bed_history %}
<tr> <tr>
<td class="fw-bold">Ward:</td>
<td> <td>
<div>{{ history.date|date:"M d, Y" }}</div> <a href="{% url 'inpatients:ward_detail' object.ward.pk %}">
<div class="small text-muted">{{ history.date|time:"g:i A" }}</div> {{ object.ward.name }}
</a>
</td> </td>
</tr>
<tr>
<td class="fw-bold">Room:</td>
<td>{{ object.room_number|default:"Not assigned" }}</td>
</tr>
<tr>
<td class="fw-bold">Bed Type:</td>
<td>{{ object.get_bed_type_display }}</td>
</tr>
<tr>
<td class="fw-bold">Status:</td>
<td> <td>
<span class="badge bg-{% if history.action == 'admission' %}success{% elif history.action == 'discharge' %}info{% elif history.action == 'transfer' %}warning{% else %}secondary{% endif %}"> <span class="badge bg-{% if object.status == 'available' %}success{% elif object.status == 'occupied' %}warning{% elif object.status == 'maintenance' %}danger{% else %}info{% endif %}">
{{ history.get_action_display }} {{ object.get_status_display }}
</span> </span>
</td> </td>
</tr>
</table>
</div>
<div class="col-md-6">
<table class="table table-borderless">
<tr>
<td class="fw-bold">Features:</td>
<td> <td>
{% if history.patient %} {% if object.has_oxygen %}
<a href="{% url 'patients:patient_detail' history.patient.pk %}"> <span class="badge bg-info me-1">Oxygen</span>
{{ history.patient.get_full_name }}
</a>
<div class="small text-muted">{{ history.patient.medical_record_number }}</div>
{% else %}
<span class="text-muted">System action</span>
{% endif %} {% endif %}
</td> {% if object.has_suction %}
<td> <span class="badge bg-info me-1">Suction</span>
{% if history.duration %}
{{ history.duration }}
{% else %}
<span class="text-muted">-</span>
{% endif %} {% endif %}
</td> {% if object.has_monitor %}
<td> <span class="badge bg-info me-1">Monitor</span>
{% if history.notes %} {% endif %}
<span class="text-truncate" style="max-width: 200px;" title="{{ history.notes }}"> {% if object.is_isolation %}
{{ history.notes }} <span class="badge bg-warning me-1">Isolation</span>
</span> {% endif %}
{% else %} {% if not object.has_oxygen and not object.has_suction and not object.has_monitor and not object.is_isolation %}
<span class="text-muted">-</span> <span class="text-muted">Standard bed</span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endfor %} <tr>
</tbody> <td class="fw-bold">Last Cleaned:</td>
</table> <td>{{ object.last_cleaned|date:"M d, Y g:i A"|default:"Not recorded" }}</td>
</tr>
<tr>
<td class="fw-bold">Last Maintenance:</td>
<td>{{ object.last_maintenance|date:"M d, Y g:i A"|default:"Not recorded" }}</td>
</tr>
<tr>
<td class="fw-bold">Created:</td>
<td>{{ object.created_at|date:"M d, Y g:i A" }}</td>
</tr>
<tr>
<td class="fw-bold">Last Updated:</td>
<td>{{ object.updated_at|date:"M d, Y g:i A" }}</td>
</tr>
</table>
</div>
</div> </div>
{% else %}
<div class="text-center text-muted py-4">
<i class="fas fa-history fa-3x mb-3"></i>
<h5>No History Available</h5>
<p>No bed history records found for this bed.</p>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-xl-4"> {% if object.notes %}
<!-- Quick Actions --> <div class="mt-3">
<div class="card mb-4"> <h6>Notes:</h6>
<div class="card-header"> <div class="note alert-info">
<h5 class="card-title"> {{ object.notes }}
<i class="fas fa-bolt me-2"></i> </div>
Quick Actions </div>
</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if object.status == 'available' %}
<button type="button" class="btn btn-success" onclick="assignPatient()">
<i class="fas fa-user-plus me-2"></i>Assign Patient
</button>
{% elif object.status == 'occupied' %}
<button type="button" class="btn btn-warning" onclick="dischargePatient()">
<i class="fas fa-sign-out-alt me-2"></i>Discharge Patient
</button>
<button type="button" class="btn btn-info" onclick="transferPatient()">
<i class="fas fa-exchange-alt me-2"></i>Transfer Patient
</button>
{% endif %} {% endif %}
<button type="button" class="btn btn-outline-primary" onclick="scheduleCleaning()">
<i class="fas fa-broom me-2"></i>Schedule Cleaning
</button>
<button type="button" class="btn btn-outline-secondary" onclick="scheduleMaintenance()">
<i class="fas fa-tools me-2"></i>Schedule Maintenance
</button>
<button type="button" class="btn btn-outline-info" onclick="viewBedChart()">
<i class="fas fa-chart-line me-2"></i>View Utilization
</button>
</div> </div>
</div> </div>
</div>
<!-- Bed Statistics --> <!-- Current Patient -->
<div class="card mb-4"> {% if object.current_admission.patient %}
<div class="card-header"> <div class="panel panel-inverse" data-sortable-id="index-2">
<h5 class="card-title"> <div class="panel-heading">
<i class="fas fa-chart-bar me-2"></i> <h4 class="panel-title">
Statistics <i class="fas fa-user-injured me-2"></i>Current Patient
</h5> </h4>
</div> <div class="panel-heading-btn">
<div class="card-body"> <a href="{% url 'inpatients:admission_detail' object.current_admission.pk %}" class="btn btn-xs btn-outline-theme">
<div class="mb-3"> <i class="fas fa-eye me-2"></i>View Admission
<div class="d-flex justify-content-between mb-1"> </a>
<span>Occupancy Rate (30 days)</span> <a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<span>{{ bed_stats.occupancy_rate|default:0 }}%</span> <a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
</div> <a href="javascript:" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<div class="progress"> <a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
<div class="progress-bar bg-primary" style="width: {{ bed_stats.occupancy_rate|default:0 }}%"></div>
</div> </div>
</div> </div>
<div class="panel-body">
<div class="row text-center"> <div class="d-flex align-items-center">
<div class="col-6">
<div class="border-end">
<div class="fs-4 fw-bold text-primary">{{ bed_stats.total_admissions|default:0 }}</div>
<div class="small text-muted">Total Admissions</div>
</div>
</div>
<div class="col-6">
<div class="fs-4 fw-bold text-success">{{ bed_stats.avg_stay_days|default:0 }}</div>
<div class="small text-muted">Avg Stay (days)</div>
</div>
</div>
</div>
</div>
<!-- Maintenance Schedule -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title">
<i class="fas fa-calendar-check me-2"></i>
Maintenance Schedule
</h5>
</div>
<div class="card-body">
{% if maintenance_schedule %}
{% for maintenance in maintenance_schedule %}
<div class="d-flex align-items-center mb-3">
<div class="flex-shrink-0">
<i class="fas fa-{% if maintenance.type == 'cleaning' %}broom text-primary{% elif maintenance.type == 'inspection' %}search text-info{% else %}tools text-warning{% endif %} fa-lg"></i>
</div>
<div class="flex-grow-1 ms-3"> <div class="flex-grow-1 ms-3">
<div class="fw-bold">{{ maintenance.get_type_display }}</div> <h5 class="mb-1">
<div class="small text-muted">{{ maintenance.scheduled_date|date:"M d, Y g:i A" }}</div> <a href="{% url 'patients:patient_detail' object.current_admission.patient.pk %}">
</div> {{ object.current_admission.patient.get_full_name }}
<div class="flex-shrink-0"> </a>
<span class="badge bg-{% if maintenance.status == 'pending' %}warning{% elif maintenance.status == 'completed' %}success{% else %}secondary{% endif %}"> </h5>
{{ maintenance.get_status_display }} <div class="text-muted mb-1">
</span> <strong>MRN:</strong> {{ object.current_admission.patient.mrn }}
</div>
<div class="text-muted mb-1">
<strong>DOB:</strong> {{ object.current_admission.patient.date_of_birth|date:"M d, Y" }}
</div>
<div class="text-muted">
<strong>Admitted:</strong> {{ object.current_admission.admission_datetime|date:"M d, Y g:i A" }}
</div>
</div> </div>
</div> </div>
{% endfor %} </div>
{% else %} </div>
<div class="text-center text-muted"> {% endif %}
<i class="fas fa-calendar-check fa-2x mb-2"></i>
<div>No scheduled maintenance</div> <!-- Bed History -->
<div class="panel panel-inverse" data-sortable-id="index-3">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-history me-2"></i>Bed History
</h4>
<div class="panel-heading-btn">
<button type="button" class="btn btn-xs btn-outline-primary me-2" onclick="refreshHistory()">
<i class="fas fa-sync me-2"></i>Refresh
</button>
<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>
{% endif %} </div>
<div class="panel-body">
{% if admission_history %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Date</th>
<th>Action</th>
<th>Patient</th>
<th>Duration</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{% for history in admission_history %}
<tr>
<td>
<div>{{ history.admission_datetime|date:"M d, Y" }}</div>
<div class="small text-muted">{{ history.admission_datetime|time:"g:i A" }}</div>
</td>
<td>
<span class="badge bg-{% if history.action == 'ADMISSION' %}success{% elif history.action == 'DISCHARGE' %}info{% elif history.action == 'TRANSFER' %}warning{% else %}secondary{% endif %}">
{{ history.get_action_display }}
</span>
</td>
<td>
{% if history.patient %}
<a href="{% url 'patients:patient_detail' history.patient.pk %}">
{{ history.patient.get_full_name }}
</a>
<div class="small text-muted">{{ history.patient.mrn }}</div>
{% else %}
<span class="text-muted">System action</span>
{% endif %}
</td>
<td>
{% if history.duration %}
{{ history.duration }}
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if history.notes %}
<span class="text-truncate" style="max-width: 200px;" title="{{ history.notes }}">
{{ history.notes }}
</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center text-muted py-4">
<i class="fas fa-history fa-3x mb-3"></i>
<h5>No History Available</h5>
<p>No bed history records found for this bed.</p>
</div>
{% endif %}
</div>
</div> </div>
</div> </div>
<!-- Ward Information --> <div class="col-xl-3">
<div class="card"> <!-- Quick Actions -->
<div class="card-header"> <div class="panel panel-inverse" data-sortable-id="index-4">
<h5 class="card-title"> <div class="panel-heading">
<i class="fas fa-hospital me-2"></i> <h4 class="panel-title">
Ward Information <i class="fas fa-bolt me-2"></i>Quick Actions
</h5> </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="d-grid gap-2">
{% if object.status == 'AVAILABLE' %}
<button type="button" class="btn btn-sm btn-outline-success" onclick="assignPatient()">
<i class="fas fa-user-plus me-2"></i>Assign Patient
</button>
{% elif object.status == 'OCCUPIED' %}
<button type="button" class="btn btn-sm btn-outline-warning" onclick="dischargePatient()">
<i class="fas fa-sign-out-alt me-2"></i>Discharge Patient
</button>
<button type="button" class="btn btn-sm btn-outline-info" onclick="transferPatient()">
<i class="fas fa-exchange-alt me-2"></i>Transfer Patient
</button>
{% endif %}
<button type="button" class="btn btn-sm btn-outline-primary" onclick="scheduleCleaning()">
<i class="fas fa-broom me-2"></i>Schedule Cleaning
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="scheduleMaintenance()">
<i class="fas fa-tools me-2"></i>Schedule Maintenance
</button>
<button type="button" class="btn btn-sm btn-outline-info" onclick="viewBedChart()">
<i class="fas fa-chart-line me-2"></i>View Utilization
</button>
</div>
</div>
</div> </div>
<div class="card-body">
<div class="mb-2"> <!-- Bed Statistics -->
<strong>Ward:</strong> <div class="panel panel-inverse" data-sortable-id="index-5">
<a href="{% url 'inpatients:ward_detail' object.ward.pk %}"> <div class="panel-heading">
{{ object.ward.name }} <h4 class="panel-title">
</a> <i class="fas fa-chart-bar me-2"></i>Statistics
</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>
<div class="mb-2"> <div class="panel-body">
<strong>Department:</strong> {{ object.ward.department.name }} <div class="mb-3">
<div class="d-flex justify-content-between mb-1">
<span>Occupancy Rate (30 days)</span>
<span>{{ bed_stats.occupancy_rate|default:0 }}%</span>
</div>
<div class="progress">
<div class="progress-bar bg-primary" style="width: {{ bed_stats.occupancy_rate|default:0 }}%"></div>
</div>
</div>
<div class="row text-center">
<div class="col-6">
<div class="border-end">
<div class="fs-4 fw-bold text-primary">{{ bed_stats.total_admissions|default:0 }}</div>
<div class="small text-muted">Total Admissions</div>
</div>
</div>
<div class="col-6">
<div class="fs-4 fw-bold text-success">{{ bed_stats.avg_stay_days|default:0 }}</div>
<div class="small text-muted">Avg Stay (days)</div>
</div>
</div>
</div> </div>
<div class="mb-2"> </div>
<strong>Capacity:</strong> {{ object.ward.total_beds }} beds
<!-- Maintenance Schedule -->
<div class="panel panel-inverse" data-sortable-id="index-6">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-calendar-check me-2"></i>Maintenance Schedule
</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>
<div class="mb-2"> <div class="panel-body">
<strong>Available:</strong> {{ object.ward.available_beds }} beds {% if maintenance_schedule %}
{% for maintenance in maintenance_schedule %}
<div class="d-flex align-items-center mb-3">
<div class="flex-shrink-0">
<i class="fas fa-{% if maintenance.type == 'cleaning' %}broom text-primary{% elif maintenance.type == 'inspection' %}search text-info{% else %}tools text-warning{% endif %} fa-lg"></i>
</div>
<div class="flex-grow-1 ms-3">
<div class="fw-bold">{{ maintenance.get_type_display }}</div>
<div class="small text-muted">{{ maintenance.scheduled_date|date:"M d, Y g:i A" }}</div>
</div>
<div class="flex-shrink-0">
<span class="badge bg-{% if maintenance.status == 'pending' %}warning{% elif maintenance.status == 'completed' %}success{% else %}secondary{% endif %}">
{{ maintenance.get_status_display }}
</span>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center text-muted">
<i class="fas fa-calendar-check fa-2x mb-2"></i>
<div>No scheduled maintenance</div>
</div>
{% endif %}
</div> </div>
<div> </div>
<strong>Occupancy:</strong> {{ object.ward.occupancy_rate }}%
<!-- Ward Information -->
<div class="panel panel-inverse" data-sortable-id="index-7">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-hospital me-2"></i>Ward Information
</h4>
<div class="panel-heading-btn">
<a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<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="mb-2">
<strong>Ward:</strong>
<a href="{% url 'inpatients:ward_detail' object.ward.pk %}">
{{ object.ward.name }}
</a>
</div>
<div class="mb-2">
<strong>Department:</strong> {{ object.ward.department.name }}
</div>
<div class="mb-2">
<strong>Capacity:</strong> {{ object.ward.total_beds }} beds
</div>
<div class="mb-2">
<strong>Available:</strong> {{ object.ward.available_beds }} beds
</div>
<div>
<strong>Occupancy:</strong> {{ object.ward.occupancy_rate|floatformat:0 }}%
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Update Status Modal --> <!-- Update Status Modal -->
<div class="modal fade" id="updateStatusModal" tabindex="-1"> <div class="modal fade" id="updateStatusModal" tabindex="-1">
<div class="modal-dialog"> <div class="modal-dialog">

View File

@ -4,21 +4,22 @@
{% block title %}Bed Management{% endblock %} {% block title %}Bed Management{% endblock %}
{% block content %} {% block content %}
<div class="d-flex align-items-center mb-3"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div> <div>
<ol class="breadcrumb"> <h1 class="h2">
<li class="breadcrumb-item"><a href="{% url 'inpatients:dashboard' %}">Inpatients</a></li> <i class="fas fa-list me-2"></i>Bed<span class="fw-light">Management</span>
<li class="breadcrumb-item active">Bed Management</li> </h1>
</ol> <p class="text-muted">Real-time bed occupancy and availability management.</p>
<h1 class="page-header mb-0">Bed Management</h1>
</div> </div>
<div class="ms-auto"> <div class="btn-toolbar mb-2 mb-md-0">
<div class="ms-auto">
<div class="btn-group"> <div class="btn-group">
<a href="{% url 'inpatients:bed_form' %}" class="btn btn-primary"> <a href="{% url 'inpatients:bed_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Add Bed <i class="fas fa-plus me-2"></i>Add Bed
</a> </a>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown"> <button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle Dropdown</span> <span class="visually-hidden">Toggle Dropdown</span>
<i class="fas fa-ellipsis-v"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportBeds()"> <li><a class="dropdown-item" href="#" onclick="exportBeds()">
@ -34,94 +35,27 @@
</ul> </ul>
</div> </div>
</div> </div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6">
<div class="card bg-primary text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="fs-6 text-white-75">Total Beds</div>
<div class="fs-2 fw-bold">{{ total_beds|default:0 }}</div>
</div>
<div class="flex-shrink-0">
<i class="fas fa-bed fa-2x text-white-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="fs-6 text-white-75">Available</div>
<div class="fs-2 fw-bold">{{ available_beds|default:0 }}</div>
</div>
<div class="flex-shrink-0">
<i class="fas fa-check-circle fa-2x text-white-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card bg-warning text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="fs-6 text-white-75">Occupied</div>
<div class="fs-2 fw-bold">{{ occupied_beds|default:0 }}</div>
</div>
<div class="flex-shrink-0">
<i class="fas fa-user fa-2x text-white-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="card bg-info text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="fs-6 text-white-75">Occupancy Rate</div>
<div class="fs-2 fw-bold">{{ occupancy_rate|default:0 }}%</div>
</div>
<div class="flex-shrink-0">
<i class="fas fa-chart-pie fa-2x text-white-50"></i>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card"> <div class="panel panel-inverse" data-sortable-id="index-1">
<div class="card-header"> <div class="panel-heading">
<h4 class="card-title"> <h4 class="panel-title">
<i class="fas fa-bed me-2"></i> <i class="fa fa-bed me-2"></i>Bed List
Bed List </h4>
</h4> <div class="panel-heading-btn">
<div class="card-toolbar"> <a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<div class="d-flex align-items-center"> <a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<!-- View Toggle --> <a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<div class="btn-group btn-group-sm me-3" role="group"> <a href="javascript:;" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
<input type="radio" class="btn-check" name="viewType" id="gridView" autocomplete="off" checked> </div>
<label class="btn btn-outline-secondary" for="gridView"> </div>
<i class="fas fa-th"></i> <div class="panel-body ">
</label> <div class="d-flex align-items-center mb-3">
<input type="radio" class="btn-check" name="viewType" id="listView" autocomplete="off">
<label class="btn btn-outline-secondary" for="listView">
<i class="fas fa-list"></i>
</label>
</div>
<!-- Filters --> <!-- Filters -->
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<select class="form-select form-select-sm me-2" id="wardFilter" onchange="filterBeds()"> <select class="form-select form-select-sm me-2" id="wardFilter" onchange="filterBeds()">
@ -132,127 +66,24 @@
</select> </select>
<select class="form-select form-select-sm me-2" id="statusFilter" onchange="filterBeds()"> <select class="form-select form-select-sm me-2" id="statusFilter" onchange="filterBeds()">
<option value="">All Status</option> <option value="">All Status</option>
<option value="available">Available</option> <option value="AVAILABLE">Available</option>
<option value="occupied">Occupied</option> <option value="OCCUPIED">Occupied</option>
<option value="maintenance">Maintenance</option> <option value="MAINTENANCE">Maintenance</option>
<option value="cleaning">Cleaning</option> <option value="CLEANING">Cleaning</option>
</select> </select>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearFilters()"> <button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearFilters()">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
</div> </div>
</div> </div>
</div>
</div>
<div class="card-body">
<!-- Grid View -->
<div id="gridViewContent">
{% if object_list %}
<div class="row" id="bedGrid">
{% for bed in object_list %}
<div class="col-xl-3 col-lg-4 col-md-6 mb-4 bed-item"
data-ward="{{ bed.ward.id }}"
data-status="{{ bed.status }}"
data-bed-type="{{ bed.bed_type }}">
<div class="card h-100 bed-card {% if bed.status == 'occupied' %}border-warning{% elif bed.status == 'available' %}border-success{% elif bed.status == 'maintenance' %}border-danger{% else %}border-info{% endif %}">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="card-title mb-0">
<i class="fas fa-bed me-2"></i>
Bed {{ bed.bed_number }}
</h6>
<span class="badge bg-{% if bed.status == 'available' %}success{% elif bed.status == 'occupied' %}warning{% elif bed.status == 'maintenance' %}danger{% else %}info{% endif %}">
{{ bed.get_status_display }}
</span>
</div>
<div class="card-body">
<div class="mb-2">
<small class="text-muted">Ward:</small>
<div class="fw-bold">{{ bed.ward.name }}</div>
</div>
<div class="mb-2">
<small class="text-muted">Room:</small>
<div>{{ bed.room_number|default:"Not assigned" }}</div>
</div>
<div class="mb-2">
<small class="text-muted">Type:</small>
<div>{{ bed.get_bed_type_display }}</div>
</div>
{% if bed.current_patient %}
<div class="mb-2">
<small class="text-muted">Patient:</small>
<div class="fw-bold text-primary">
<a href="{% url 'patients:patient_detail' bed.current_patient.pk %}">
{{ bed.current_patient.get_full_name }}
</a>
</div>
<small class="text-muted">{{ bed.current_patient.medical_record_number }}</small>
</div>
{% endif %}
<div class="mb-3">
{% if bed.has_oxygen %}
<span class="badge bg-info me-1">O2</span>
{% endif %}
{% if bed.has_suction %}
<span class="badge bg-info me-1">Suction</span>
{% endif %}
{% if bed.has_monitor %}
<span class="badge bg-info me-1">Monitor</span>
{% endif %}
{% if bed.is_isolation %}
<span class="badge bg-warning me-1">Isolation</span>
{% endif %}
</div>
</div>
<div class="card-footer">
<div class="d-flex justify-content-between">
<a href="{% url 'inpatients:bed_detail' bed.pk %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye me-1"></i>View
</a>
<div class="btn-group">
<a href="{% url 'inpatients:bed_form' bed.pk %}" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-edit"></i>
</a>
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('{{ bed.pk }}', 'available')">
<i class="fas fa-check text-success me-2"></i>Mark Available
</a></li>
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('{{ bed.pk }}', 'maintenance')">
<i class="fas fa-tools text-warning me-2"></i>Maintenance
</a></li>
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('{{ bed.pk }}', 'cleaning')">
<i class="fas fa-broom text-info me-2"></i>Cleaning
</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center text-muted py-5">
<i class="fas fa-bed fa-3x mb-3"></i>
<h5>No Beds Found</h5>
<p>No beds match your current filters. Try adjusting your search criteria.</p>
<a href="{% url 'inpatients:bed_form' %}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Add First Bed
</a>
</div>
{% endif %}
</div>
<!-- List View --> <!-- List View -->
<div id="listViewContent" style="display: none;"> <div id="listViewContent">
{% if object_list %} {% if object_list %}
<div class="table-responsive"> <div class="table-responsive border border-primary-subtle">
<table class="table table-hover" id="bedTable"> <table class="table table-hover" id="bedTable">
<thead> <thead class="table-primary">
<tr> <tr>
<th> <th>
<input type="checkbox" id="selectAll" onchange="toggleSelectAll()"> <input type="checkbox" id="selectAll" onchange="toggleSelectAll()">
@ -269,8 +100,8 @@
</thead> </thead>
<tbody> <tbody>
{% for bed in object_list %} {% for bed in object_list %}
<tr class="bed-row" <tr class="bed-row"
data-ward="{{ bed.ward.id }}" data-ward="{{ bed.ward.id }}"
data-status="{{ bed.status }}" data-status="{{ bed.status }}"
data-bed-type="{{ bed.bed_type }}"> data-bed-type="{{ bed.bed_type }}">
<td> <td>
@ -290,11 +121,11 @@
</span> </span>
</td> </td>
<td> <td>
{% if bed.current_patient %} {% if bed.current_admission.patient %}
<a href="{% url 'patients:patient_detail' bed.current_patient.pk %}"> <a href="{% url 'patients:patient_detail' bed.current_admission.patient.pk %}">
{{ bed.current_patient.get_full_name }} {{ bed.current_admission.patient.get_full_name }}
</a> </a>
<div class="small text-muted">{{ bed.current_patient.medical_record_number }}</div> <div class="small text-muted">{{ bed.current_admission.patient.mrn }}</div>
{% else %} {% else %}
<span class="text-muted">-</span> <span class="text-muted">-</span>
{% endif %} {% endif %}
@ -318,7 +149,7 @@
<a href="{% url 'inpatients:bed_detail' bed.pk %}" class="btn btn-outline-primary"> <a href="{% url 'inpatients:bed_detail' bed.pk %}" class="btn btn-outline-primary">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</a> </a>
<a href="{% url 'inpatients:bed_form' bed.pk %}" class="btn btn-outline-secondary"> <a href="{% url 'inpatients:bed_update' bed.pk %}" class="btn btn-outline-secondary">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</a> </a>
<button type="button" class="btn btn-outline-danger" onclick="deleteBed('{{ bed.pk }}')"> <button type="button" class="btn btn-outline-danger" onclick="deleteBed('{{ bed.pk }}')">
@ -342,29 +173,9 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() {
// View toggle functionality
const gridViewRadio = document.getElementById('gridView');
const listViewRadio = document.getElementById('listView');
const gridViewContent = document.getElementById('gridViewContent');
const listViewContent = document.getElementById('listViewContent');
gridViewRadio.addEventListener('change', function() {
if (this.checked) {
gridViewContent.style.display = 'block';
listViewContent.style.display = 'none';
}
});
listViewRadio.addEventListener('change', function() {
if (this.checked) {
gridViewContent.style.display = 'none';
listViewContent.style.display = 'block';
}
});
});
function filterBeds() { function filterBeds() {
const wardFilter = document.getElementById('wardFilter').value; const wardFilter = document.getElementById('wardFilter').value;

File diff suppressed because it is too large Load Diff

View File

@ -171,7 +171,7 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="planning-header"> <div class="planning-header">
<div class="row"> <div class="row mb-2">
<div class="col-md-8"> <div class="col-md-8">
<h5><i class="fas fa-user"></i> Patient: {{ admission.patient.get_full_name }}</h5> <h5><i class="fas fa-user"></i> Patient: {{ admission.patient.get_full_name }}</h5>
<p><strong>MRN:</strong> {{ admission.patient.mrn }} | <strong>Age:</strong> {{ admission.patient.age }} | <strong>Gender:</strong> <p><strong>MRN:</strong> {{ admission.patient.mrn }} | <strong>Age:</strong> {{ admission.patient.age }} | <strong>Gender:</strong>
@ -189,33 +189,46 @@
<p class="mt-2 mb-0">Expected Discharge: {{ admission.anticipated_discharge_date}}</p> <p class="mt-2 mb-0">Expected Discharge: {{ admission.anticipated_discharge_date}}</p>
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-8"></div>
<div class="col-md-4">
<button type="button" class="btn btn-outline-theme btn-sm me-2">
<i class="fas fa-check"></i> Approve Discharge Plan
</button>
<button type="button" class="btn btn-outline-warning btn-sm me-2">
<i class="fas fa-edit"></i> Modify Plan
</button>
<button type="button" class="btn btn-outline-info btn-sm me-2">
<i class="fas fa-share"></i> Share with Team
</button>
<button type="button" class="btn btn-outline-danger btn-sm">
<i class="fas fa-save"></i> Save Draft
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-clipboard-list me-2"></i>Discharge Plan
</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">
<!-- Main Planning Content --> <!-- Main Planning Content -->
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-8">
<!-- Discharge Timeline --> <!-- Discharge Timeline -->
<div class="panel panel-inverse border border-primary"> <div class="panel panel-inverse">
<div class="panel-heading bg-primary"> <div class="panel-heading">
<h4 class="panel-title"><i class="fas fa-timeline"></i> Discharge Timeline</h4> <h4 class="panel-title">
<i class="fas fa-timeline"></i> Discharge Timeline
</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>
<div class="panel-body"> <div class="panel-body">
<div class="timeline"> <div class="timeline">
{% %}
<div class="timeline-item"> <div class="timeline-item">
<div class="timeline-time"> <div class="timeline-time">
<span class="date">today</span> <span class="date">today</span>
@ -242,11 +255,19 @@
</div> </div>
<!-- Discharge Checklist --> <!-- Discharge Checklist -->
<div class="card mt-3"> <div class="panel panel-inverse">
<div class="card-header"> <div class="panel-heading">
<h5><i class="fas fa-tasks"></i> Discharge Checklist</h5> <h4 class="panel-title">
<i class="fas fa-tasks"></i> Discharge Checklist
</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>
<div class="card-body"> <div class="panel-body">
<div class="mb-2"> <div class="mb-2">
<h6>Medical Clearance</h6> <h6>Medical Clearance</h6>
<div class="checklist-item"> <div class="checklist-item">
@ -332,11 +353,19 @@
<div class="col-md-4"> <div class="col-md-4">
<!-- Risk Assessment --> <!-- Risk Assessment -->
<div class="card"> <div class="panel panel-inverse">
<div class="card-header"> <div class="panel-heading">
<h5><i class="fas fa-exclamation-triangle"></i> Risk Assessment</h5> <h4 class="panel-title">
</div> <i class="fas fa-exclamation-triangle"></i> Risk Assessment
<div class="card-body"> </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="alert-item alert-medium"> <div class="alert-item alert-medium">
<strong>Readmission Risk:</strong> Moderate <strong>Readmission Risk:</strong> Moderate
<br><small>Age >65, cardiac condition, multiple medications</small> <br><small>Age >65, cardiac condition, multiple medications</small>
@ -357,11 +386,19 @@
</div> </div>
<!-- Care Team --> <!-- Care Team -->
<div class="card mt-3"> <div class="panel panel-inverse">
<div class="card-header"> <div class="panel-heading">
<h5><i class="fas fa-users"></i> Discharge Care Team</h5> <h4 class="panel-title">
<i class="fas fa-users"></i> Discharge Care Team
</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>
<div class="card-body"> <div class="panel-body">
<div class="care-team"> <div class="care-team">
<div class="team-member"> <div class="team-member">
<i class="fas fa-user-md fa-2x text-primary mb-2"></i> <i class="fas fa-user-md fa-2x text-primary mb-2"></i>
@ -388,11 +425,19 @@
</div> </div>
<!-- Quick Actions --> <!-- Quick Actions -->
<div class="card mt-3"> <div class="panel panel-inverse">
<div class="card-header"> <div class="panel-heading">
<h5><i class="fas fa-bolt"></i> Quick Actions</h5> <h4 class="panel-title">
<i class="fas fa-bolt"></i> Quick Actions
</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>
<div class="card-body"> <div class="panel-body">
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<button class="btn btn-outline-primary btn-sm"> <button class="btn btn-outline-primary btn-sm">
<i class="fas fa-calendar"></i> Schedule Follow-up <i class="fas fa-calendar"></i> Schedule Follow-up
@ -415,11 +460,19 @@
<!-- Discharge Medications --> <!-- Discharge Medications -->
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="card"> <div class="panel panel-inverse">
<div class="card-header"> <div class="panel-heading">
<h5><i class="fas fa-pills"></i> Discharge Medications</h5> <h4 class="panel-title">
<i class="fas fa-pills"></i> Discharge Medications
</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>
<div class="card-body"> <div class="panel-body">
<div class="medication-item"> <div class="medication-item">
<h6>Aspirin 81mg</h6> <h6>Aspirin 81mg</h6>
<p>Take 1 tablet daily by mouth</p> <p>Take 1 tablet daily by mouth</p>
@ -445,11 +498,19 @@
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="card"> <div class="panel panel-inverse">
<div class="card-header"> <div class="panel-heading">
<h5><i class="fas fa-calendar-check"></i> Follow-up Appointments</h5> <h4 class="panel-title">
<i class="fas fa-calendar-check"></i> Follow-up Appointments
</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>
<div class="card-body"> <div class="panel-body">
<div class="appointment-item"> <div class="appointment-item">
<h6>Cardiology Follow-up</h6> <h6>Cardiology Follow-up</h6>
<p><strong>Date:</strong> January 30, 2024 at 2:00 PM</p> <p><strong>Date:</strong> January 30, 2024 at 2:00 PM</p>
@ -481,25 +542,13 @@
<div class="col-12"> <div class="col-12">
<div class="card"> <div class="card">
<div class="card-body text-center"> <div class="card-body text-center">
<button type="button" class="btn btn-success btn-lg me-3">
<i class="fas fa-check"></i> Approve Discharge Plan
</button>
<button type="button" class="btn btn-warning btn-lg me-3">
<i class="fas fa-edit"></i> Modify Plan
</button>
<button type="button" class="btn btn-info btn-lg me-3">
<i class="fas fa-share"></i> Share with Team
</button>
<button type="button" class="btn btn-secondary btn-lg">
<i class="fas fa-save"></i> Save Draft
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,84 @@
<div class="row">
<div class="col-md-6">
<table class="table table-borderless">
<tr>
<td class="fw-bold">Bed Number:</td>
<td>{{ object.bed_number }}</td>
</tr>
<tr>
<td class="fw-bold">Ward:</td>
<td>
<a href="{% url 'inpatients:ward_detail' object.ward.pk %}">
{{ object.ward.name }}
</a>
</td>
</tr>
<tr>
<td class="fw-bold">Room:</td>
<td>{{ object.room_number|default:"Not assigned" }}</td>
</tr>
<tr>
<td class="fw-bold">Bed Type:</td>
<td>{{ object.get_bed_type_display }}</td>
</tr>
<tr>
<td class="fw-bold">Status:</td>
<td>
<span class="badge bg-{% if object.status == 'AVAILABLE' %}success{% elif object.status == 'OCCUPIED' %}primary{% elif object.status == 'MAINTENANCE' %}warning{% elif object.status == 'RESERVED' %}purple{% elif object.status == 'BLOCKED' %}red{% else %}info{% endif %}">
{{ object.get_status_display }}
</span>
</td>
</tr>
</table>
</div>
<div class="col-md-6">
<table class="table table-borderless">
<tr>
<td class="fw-bold">Features:</td>
<td>
{% if object.has_oxygen %}
<span class="badge bg-info me-1">Oxygen</span>
{% endif %}
{% if object.has_suction %}
<span class="badge bg-info me-1">Suction</span>
{% endif %}
{% if object.has_monitor %}
<span class="badge bg-info me-1">Monitor</span>
{% endif %}
{% if object.is_isolation %}
<span class="badge bg-warning me-1">Isolation</span>
{% endif %}
{% if not object.has_oxygen and not object.has_suction and not object.has_monitor and not object.is_isolation %}
<span class="text-muted">Standard bed</span>
{% endif %}
</td>
</tr>
<tr>
<td class="fw-bold">Last Cleaned:</td>
<td>{{ object.last_cleaned|date:"M d, Y g:i A"|default:"Not recorded" }}</td>
</tr>
<tr>
<td class="fw-bold">Last Maintenance:</td>
<td>{{ object.last_maintenance|date:"M d, Y g:i A"|default:"Not recorded" }}</td>
</tr>
<tr>
<td class="fw-bold">Created:</td>
<td>{{ object.created_at|date:"M d, Y g:i A" }}</td>
</tr>
<tr>
<td class="fw-bold">Last Updated:</td>
<td>{{ object.updated_at|date:"M d, Y g:i A" }}</td>
</tr>
</table>
</div>
</div>
{% if object.notes %}
<div class="mt-3">
<h6>Notes:</h6>
<div class="note alert-info">
{{ object.notes }}
</div>
</div>
{% endif %}

View File

@ -5,7 +5,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="updateBedStatusForm" method="post" action="{% url 'inpatients:update_bed_status' bed_id=bed.id %}"> <form id="updateBedStatusForm" method="post" action="{% url 'inpatients:update_bed_status' bed.id %}">
{% csrf_token %} {% csrf_token %}
<div class="bed-info mb-3"> <div class="bed-info mb-3">
@ -26,9 +26,9 @@
</div> </div>
</div> </div>
{% if bed.current_patient %} {% if bed.current_admission.patient %}
<div class="alert alert-warning mt-2 mb-0"> <div class="alert alert-warning mt-2 mb-0">
<i class="fa fa-exclamation-triangle"></i> This bed is currently occupied by <strong>{{ bed.current_patient.get_full_name }}</strong>. <i class="fa fa-exclamation-triangle"></i> This bed is currently occupied by <strong>{{ bed.current_admission.patient.get_full_name }}</strong>.
Changing the status will affect the patient's admission. Changing the status will affect the patient's admission.
</div> </div>
{% endif %} {% endif %}

View File

@ -4,17 +4,35 @@
{% block title %}Surgery Schedule - {{ block.super }}{% endblock %} {% block title %}Surgery Schedule - {{ 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-procedures me-2"></i></i>Surgeries<span class="fw-light">Schedule</span>
</h1>
<p class="text-muted">Comprehensive discharge planning and coordination.</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="row">
<div class="col-12"> <div class="col-12">
<div class="card"> <div class="panel panel-inverse">
<div class="card-header"> <div class="panel-heading">
<h4 class="card-title mb-0"> <h4 class="panel-title">
<i class="fas fa-procedures me-2"></i>Surgery Schedule <i class="fas fa-list-dots"></i> List
</h4> </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>
<div class="panel-body">
<div class="card-body">
<!-- Filters --> <!-- Filters -->
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-2"> <div class="col-md-2">
@ -52,12 +70,13 @@
</div> </div>
<!-- Surgery Schedule Table --> <!-- Surgery Schedule Table -->
<div class="table-responsive"> <div class="table-responsive border border-primary-subtle">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead class="table-primary">
<tr> <tr>
<th>Date/Time</th> <th>Date/Time</th>
<th>Patient</th> <th>Patient</th>
<th>Room</th>
<th>Procedure</th> <th>Procedure</th>
<th>Surgeon</th> <th>Surgeon</th>
<th>Type</th> <th>Type</th>
@ -81,6 +100,12 @@
</small> </small>
</td> </td>
<td> <td>
<strong>{{ surgery.operating_room.room_name }}</strong>
{% if surgery.operating_room.room_name %}
<br><small class="text-muted">Room No.: {{ surgery.operating_room.room_number }}</small>
{% endif %}
</td>
<td>
<strong>{{ surgery.procedure_name }}</strong> <strong>{{ surgery.procedure_name }}</strong>
{% if surgery.procedure_code %} {% if surgery.procedure_code %}
<br><small class="text-muted">Code: {{ surgery.procedure_code }}</small> <br><small class="text-muted">Code: {{ surgery.procedure_code }}</small>
@ -125,9 +150,9 @@
<i class="fas fa-check"></i> <i class="fas fa-check"></i>
</button> </button>
{% elif surgery.status == 'CONFIRMED' %} {% elif surgery.status == 'CONFIRMED' %}
<button class="btn btn-outline-primary" <button class="btn btn-outline-primary"
title="Start Prep" title="Start Prep"
hx-post="{% url 'inpatients:start_surgery_prep' surgery.id %}" hx-post="{% url 'inpatients:prep_surgery' surgery.id %}"
hx-confirm="Start surgery prep?" hx-confirm="Start surgery prep?"
hx-swap="none" hx-swap="none"
hx-headers='{"X-CSRFToken":"{{ csrf_token }}"}'> hx-headers='{"X-CSRFToken":"{{ csrf_token }}"}'>
@ -153,18 +178,18 @@
</button> </button>
{% endif %} {% endif %}
<button class="btn btn-outline-info" title="View Details"> <a href="{% url 'inpatients:surgery_detail' surgery.id %}" class="btn btn-outline-info" title="View Details">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</button> </a>
{% if surgery.status not in 'COMPLETED,CANCELLED' %} {% if surgery.status not in 'COMPLETED,CANCELLED' %}
{# <button class="btn btn-outline-warning" #} <button class="btn btn-outline-warning"
{# title="Postpone"#} title="Postpone"
{# hx-post="{% url 'inpatients:s' surgery.id %}"#} hx-post="{% url 'inpatients:postpone_surgery' surgery.id %}"
{# hx-confirm="Postpone this surgery?"#} hx-confirm="Postpone this surgery?"
{# hx-swap="none">#} hx-swap="none">
{# <i class="fas fa-clock"></i>#} <i class="fas fa-clock"></i>
{# </button>#} </button>
<button class="btn btn-outline-danger" <button class="btn btn-outline-danger"
title="Cancel" title="Cancel"
hx-post="{% url 'inpatients:cancel_surgery' surgery.id %}" hx-post="{% url 'inpatients:cancel_surgery' surgery.id %}"
@ -188,38 +213,11 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div>
<!-- Pagination --> <!-- Pagination -->
{% if is_paginated %} {% if is_paginated %}
<nav aria-label="Surgery pagination"> {% include 'partial/pagination.html' %}
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1">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</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %} {% endif %}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,11 +1,8 @@
{% load static %} {% load static %}
<div class="modal-header">
<h5 class="modal-title">Transfer Patient</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <form id="transferForm" method="post" action="{% url 'inpatients:transfer_patient' admission.id %}">
</div>
<div class="modal-body">
<form id="transferForm" method="post" action="{% url 'inpatients:request_transfer' admission_id=admission.id %}">
{% csrf_token %} {% csrf_token %}
<div class="patient-info mb-3"> <div class="patient-info mb-3">
@ -103,7 +100,7 @@
</div> </div>
</div> </div>
</form> </form>
</div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="transferForm" class="btn btn-primary"> <button type="submit" form="transferForm" class="btn btn-primary">

View File

@ -4,17 +4,35 @@
{% block title %}Transfer Management - {{ block.super }}{% endblock %} {% block title %}Transfer Management - {{ 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-exchange-alt me-2"></i> Transfer<span class="fw-light">Management</span>
</h1>
<p class="text-muted">Manage all transfers</p>
</div>
<div class="btn-group">
</div>
</div>
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card"> <div class="panel panel-inverse" data-sortable-id="index-1">
<div class="card-header"> <div class="panel-heading">
<h4 class="card-title mb-0"> <h4 class="panel-title">
<i class="fas fa-exchange-alt me-2"></i>Transfer Management <i class="fas fa-exchange me-2"></i>Transfer Information
</h4> </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>
<div class="panel-body">
<div class="card-body">
<!-- Filters --> <!-- Filters -->
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-2"> <div class="col-md-2">
@ -51,9 +69,9 @@
</div> </div>
<!-- Transfers Table --> <!-- Transfers Table -->
<div class="table-responsive"> <div class="table-responsive border border-primary-subtle">
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead class="table-primary">
<tr> <tr>
<th>Patient</th> <th>Patient</th>
<th>Requested</th> <th>Requested</th>
@ -121,18 +139,23 @@
<td> <td>
<div class="btn-group btn-group-sm"> <div class="btn-group btn-group-sm">
{% if transfer.status == 'REQUESTED' %} {% if transfer.status == 'REQUESTED' %}
<a class="btn btn-outline-success" <button class="btn btn-outline-success"
title="Approve" title="Approve"
href="{% url 'inpatients:approve_transfer' transfer.id %}"> hx-post="{% url 'inpatients:approve_transfer' transfer.id %}"
hx-confirm="Approve this transfer?"
hx-swap="none"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<i class="fas fa-check"></i> <i class="fas fa-check"></i>
</a> </button>
{# <button class="btn btn-outline-danger" #}
{# title="Reject"#} <button class="btn btn-outline-danger"
{# hx-post="{% url 'inpatients:reject_transfer' transfer.id %}"#} title="Reject"
{# hx-confirm="Reject this transfer?"#} hx-post="{% url 'inpatients:reject_transfer' transfer.id %}"
{# hx-swap="none">#} hx-confirm="Reject this transfer?"
{# <i class="fas fa-times"></i>#} hx-swap="none"
{# </button>#} hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<i class="fas fa-times"></i>
</button>
{% elif transfer.status == 'APPROVED' or transfer.status == 'SCHEDULED' %} {% elif transfer.status == 'APPROVED' or transfer.status == 'SCHEDULED' %}
{# <button class="btn btn-outline-primary" #} {# <button class="btn btn-outline-primary" #}
{# title="Execute Transfer"#} {# title="Execute Transfer"#}
@ -143,18 +166,19 @@
{# </button>#} {# </button>#}
{% endif %} {% endif %}
<button class="btn btn-outline-info" title="View Details"> <a href="{% url 'inpatients:transfer_patient' transfer.admission.id%}" class="btn btn-outline-primary" title="View Details">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</button> </a>
{% if transfer.status not in 'COMPLETED,CANCELLED,REJECTED' %} {% if transfer.status not in 'COMPLETED,CANCELLED,REJECTED' %}
{# <button class="btn btn-outline-secondary" #} <button class="btn btn-outline-warning"
{# title="Cancel"#} title="Cancel"
{# hx-post="{% url 'inpatients:cancel_transfer' transfer.id %}"#} hx-post="{% url 'inpatients:cancel_transfer' transfer.id %}"
{# hx-confirm="Cancel this transfer?"#} hx-confirm="Cancel this transfer?"
{# hx-swap="none">#} hx-swap="none"
{# <i class="fas fa-ban"></i>#} hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
{# </button>#} <i class="fas fa-ban"></i>
</button>
{% endif %} {% endif %}
</div> </div>
</td> </td>
@ -170,12 +194,11 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> <!-- Pagination -->
<!-- Pagination -->
{% if is_paginated %} {% if is_paginated %}
{% include 'partial/pagination.html'%} {% include 'partial/pagination.html'%}
{% endif %} {% endif %}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,20 +4,14 @@
{% block title %}{{ ward.name }} - Ward Details - {{ block.super }}{% endblock %} {% block title %}{{ ward.name }} - Ward Details - {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid">
<!-- Page Header --> <!-- Page Header -->
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div> <div>
<h1 class="h3 mb-1"> <h1 class="h2">
<i class="fas fa-hospital me-2"></i>{{ ward.name }} <i class="fas fa-hospital me-2"></i>{{ ward.name }}<span class="fw-light">Details</span>
</h1> </h1>
<nav aria-label="breadcrumb"> <p class="text-muted">Manage ward aspects</p>
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'inpatients:dashboard' %}">Inpatients</a></li>
<li class="breadcrumb-item"><a href="{% url 'inpatients:ward_list' %}">Wards</a></li>
<li class="breadcrumb-item active">{{ ward.name }}</li>
</ol>
</nav>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<a href="{% url 'inpatients:ward_list' %}" class="btn btn-outline-secondary"> <a href="{% url 'inpatients:ward_list' %}" class="btn btn-outline-secondary">
@ -48,18 +42,25 @@
</div> </div>
</div> </div>
</div> </div>
<div class="container-fluid">
<div class="row"> <div class="row">
<!-- Main Content --> <!-- Main Content -->
<div class="col-lg-8"> <div class="col-lg-8">
<!-- Ward Information --> <!-- Ward Information -->
<div class="card mb-4"> <div class="panel panel-inverse" data-sortable-id="index-1">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-info-circle me-2"></i>Ward Information <i class="fas fa-info-circle me-2"></i>Ward Information
</h5> </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>
<div class="card-body"> <div class="panel-body">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<dl class="row"> <dl class="row">
@ -132,35 +133,37 @@
</div> </div>
<!-- Bed Layout --> <!-- Bed Layout -->
<div class="card mb-4"> <div class="panel panel-inverse" data-sortable-id="index-2">
<div class="card-header"> <div class="panel-heading">
<div class="d-flex justify-content-between align-items-center"> <h4 class="panel-title">
<h5 class="mb-0"> <i class="fas fa-bed me-2"></i>Bed Layout
<i class="fas fa-bed me-2"></i>Bed Layout </h4>
</h5> <div class="panel-heading-btn">
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary" onclick="refreshBedLayout()"> <a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<i class="fas fa-sync-alt"></i> <a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
</button> <a href="javascript:" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<button type="button" class="btn btn-outline-primary" onclick="toggleBedView()"> <a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
<i class="fas fa-th me-1"></i>Grid View
</button>
</div>
</div> </div>
</div> </div>
<div class="card-body"> <div class="panel-body">
<div id="bed-layout" class="bed-grid"> <div id="bed-layout" class="bed-grid">
{% for bed in ward.beds.all %} {% for bed in ward.beds.all %}
<div class="bed-item bed-{{ bed.status|lower }}" data-bed-id="{{ bed.id }}" onclick="showBedDetails({{ bed.id }})"> <div class="bed-item bed-{{ bed.status|lower }}" data-bed-id="{{ bed.id }}" onclick="showBedDetails({{ bed.id }})">
<div class="bed-number">{{ bed.bed_number }}</div> <div class="bed-number">{{ bed.bed_number }}</div>
<div class="bed-status"> <div class="bed-status">
<span class="badge bg-{% if bed.status == 'AVAILABLE' %}success{% elif bed.status == 'OCCUPIED' %}primary{% elif bed.status == 'MAINTENANCE' %}warning{% elif bed.status == 'RESERVED' %}info{% else %}secondary{% endif %}"> <span class="badge bg-{% if bed.status == 'AVAILABLE' %}success
{% elif bed.status == 'OCCUPIED' %}primary
{% elif bed.status == 'MAINTENANCE' %}warning
{% elif bed.status == 'RESERVED' %}purple
{% elif object.status == 'BLOCKED' %}red
{% else %}info{% endif %}">
{{ bed.get_status_display }} {{ bed.get_status_display }}
</span> </span>
</div> </div>
{% if bed.current_patient %} {% if bed.current_admission.patient %}
<div class="patient-info"> <div class="patient-info">
<small>{{ bed.current_patient.get_full_name }}</small> <small>{{ bed.current_admission.patient.get_full_name }}</small>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -179,16 +182,20 @@
</div> </div>
<!-- Current Patients --> <!-- Current Patients -->
<div class="card"> <div class="panel panel-inverse" data-sortable-id="index-3">
<div class="card-header"> <div class="panel-heading">
<div class="d-flex justify-content-between align-items-center"> <h4 class="panel-title">
<h5 class="mb-0"> <i class="fas fa-users me-2"></i>Current Patients
<i class="fas fa-users me-2"></i>Current Patients </h4>
</h5> <div class="panel-heading-btn">
<span class="badge bg-primary">{{ current_patients.count }} patients</span> <span class="badge bg-primary me-2">{{ recent_admissions.count }} patients</span>
<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> </div>
<div class="card-body p-0"> <div class="panel-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover mb-0"> <table class="table table-hover mb-0">
<thead class="table-light"> <thead class="table-light">
@ -202,10 +209,12 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for admission in current_patients %} {% for admission in recent_admissions %}
<tr> <tr>
<td> <td>
<span class="badge bg-info">{{ admission.bed.bed_number }}</span> <p class="fw-bold">{{ admission.current_bed.bed_number }}
<span class="badge bg-info">{{ admission.current_bed.bed_position }}</span>
</p>
</td> </td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
@ -216,17 +225,17 @@
</div> </div>
<div> <div>
<div class="fw-semibold">{{ admission.patient.get_full_name }}</div> <div class="fw-semibold">{{ admission.patient.get_full_name }}</div>
<small class="text-muted">{{ admission.patient.patient_id }}</small> <small class="text-muted">{{ admission.patient.mrn }}</small>
</div> </div>
</div> </div>
</td> </td>
<td> <td>
<div>{{ admission.admission_date|date:"M d, Y" }}</div> <div>{{ admission.admission_datetime|date:"M d, Y" }}</div>
<small class="text-muted">{{ admission.admission_date|timesince }} ago</small> <small class="text-muted">{{ admission.admission_datetime|timesince }} ago</small>
</td> </td>
<td> <td>
{% if admission.primary_diagnosis %} {% if admission.admitting_diagnosis %}
{{ admission.primary_diagnosis|truncatechars:30 }} {{ admission.admitting_diagnosis|truncatechars:30 }}
{% else %} {% else %}
<span class="text-muted">Not specified</span> <span class="text-muted">Not specified</span>
{% endif %} {% endif %}
@ -244,21 +253,15 @@
class="btn btn-outline-primary btn-sm" title="View Details"> class="btn btn-outline-primary btn-sm" title="View Details">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</a> </a>
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle dropdown-toggle-split" <a class="btn btn-outline-warning btn-sm" onclick="showTransfer({{ admission.id }})">
data-bs-toggle="dropdown"> <i class="fas fa-exchange-alt"></i>
<span class="visually-hidden">Toggle Dropdown</span> </a>
</button> <a class="btn btn-outline-danger btn-sm" href="{% url 'inpatients:discharge_patient' admission.id %}">
<ul class="dropdown-menu"> <i class="fas fa-sign-out-alt"></i>
<li><a class="dropdown-item" href="#"> </a>
<i class="fas fa-exchange-alt me-2"></i>Transfer <a class="btn btn-outline-info btn-sm" href="#">
</a></li> <i class="fas fa-notes-medical"></i>
<li><a class="dropdown-item" href="#"> </a>
<i class="fas fa-sign-out-alt me-2"></i>Discharge
</a></li>
<li><a class="dropdown-item" href="#">
<i class="fas fa-notes-medical me-2"></i>Add Note
</a></li>
</ul>
</div> </div>
</td> </td>
</tr> </tr>
@ -281,18 +284,24 @@
<!-- Sidebar --> <!-- Sidebar -->
<div class="col-lg-4"> <div class="col-lg-4">
<!-- Ward Statistics --> <!-- Ward Statistics -->
<div class="card mb-4"> <div class="panel panel-inverse" data-sortable-id="index-4">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-chart-bar me-2"></i>Ward Statistics <i class="fas fa-chart-bar me-2"></i>Ward Statistics
</h5> </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>
<div class="card-body"> <div class="panel-body">
<div class="row text-center"> <div class="row text-center">
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<div class="card bg-success bg-gradient text-white"> <div class="card bg-success bg-gradient text-white">
<div class="card-body"> <div class="card-body">
<h4 class="mb-1">{{ ward.available_beds }}</h4> <h4 class="mb-1">{{ available_beds }}</h4>
<small>Available</small> <small>Available</small>
</div> </div>
</div> </div>
@ -300,7 +309,7 @@
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<div class="card bg-primary bg-gradient text-white"> <div class="card bg-primary bg-gradient text-white">
<div class="card-body"> <div class="card-body">
<h4 class="mb-1">{{ ward.occupied_beds }}</h4> <h4 class="mb-1">{{ occupied_beds }}</h4>
<small>Occupied</small> <small>Occupied</small>
</div> </div>
</div> </div>
@ -308,15 +317,15 @@
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<div class="card bg-warning bg-gradient text-white"> <div class="card bg-warning bg-gradient text-white">
<div class="card-body"> <div class="card-body">
<h4 class="mb-1">{{ ward.maintenance_beds }}</h4> <h4 class="mb-1">{{ maintenance_beds }}</h4>
<small>Maintenance</small> <small>Maintenance</small>
</div> </div>
</div> </div>
</div> </div>
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<div class="card bg-info bg-gradient text-white"> <div class="card bg-purple bg-gradient text-white">
<div class="card-body"> <div class="card-body">
<h4 class="mb-1">{{ ward.reserved_beds }}</h4> <h4 class="mb-1">{{ reserved_beds }}</h4>
<small>Reserved</small> <small>Reserved</small>
</div> </div>
</div> </div>
@ -328,7 +337,7 @@
<div class="progress mb-2" style="height: 25px;"> <div class="progress mb-2" style="height: 25px;">
<div class="progress-bar bg-{% if ward.occupancy_rate > 90 %}danger{% elif ward.occupancy_rate > 75 %}warning{% else %}success{% endif %}" <div class="progress-bar bg-{% if ward.occupancy_rate > 90 %}danger{% elif ward.occupancy_rate > 75 %}warning{% else %}success{% endif %}"
style="width: {{ ward.occupancy_rate }}%"> style="width: {{ ward.occupancy_rate }}%">
{{ ward.occupancy_rate|floatformat:1 }}% {{ ward.occupancy_rate|floatformat:0 }}%
</div> </div>
</div> </div>
<small class="text-muted">{{ ward.occupied_beds }} of {{ ward.total_beds }} beds occupied</small> <small class="text-muted">{{ ward.occupied_beds }} of {{ ward.total_beds }} beds occupied</small>
@ -337,13 +346,19 @@
</div> </div>
<!-- Quick Actions --> <!-- Quick Actions -->
<div class="card mb-4"> <div class="panel panel-inverse" data-sortable-id="index-5">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-bolt me-2"></i>Quick Actions <i class="fas fa-bolt me-2"></i>Quick Actions
</h5> </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>
<div class="card-body"> <div class="panel-body">
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<a href="#" class="btn btn-primary"> <a href="#" class="btn btn-primary">
<i class="fas fa-user-plus me-2"></i>Admit Patient <i class="fas fa-user-plus me-2"></i>Admit Patient
@ -351,12 +366,12 @@
<a href="{% url 'inpatients:bed_management' %}?ward={{ ward.id }}" class="btn btn-outline-secondary"> <a href="{% url 'inpatients:bed_management' %}?ward={{ ward.id }}" class="btn btn-outline-secondary">
<i class="fas fa-bed me-2"></i>Manage Beds <i class="fas fa-bed me-2"></i>Manage Beds
</a> </a>
<a href="#" class="btn btn-outline-info"> <a href="{% url 'inpatients:transfer_management' %}" class="btn btn-outline-info">
<i class="fas fa-exchange-alt me-2"></i>Transfer Patient <i class="fas fa-exchange-alt me-2"></i>Transfer Management
</a>
<a href="#" class="btn btn-outline-success">
<i class="fas fa-sign-out-alt me-2"></i>Discharge Patient
</a> </a>
{# <a href="{% url 'inpatients:discharge_patient' %}" class="btn btn-outline-success">#}
{# <i class="fas fa-sign-out-alt me-2"></i>Discharge Management#}
{# </a>#}
<a href="#" class="btn btn-outline-warning"> <a href="#" class="btn btn-outline-warning">
<i class="fas fa-chart-bar me-2"></i>Ward Report <i class="fas fa-chart-bar me-2"></i>Ward Report
</a> </a>
@ -368,13 +383,19 @@
</div> </div>
<!-- Ward Staff --> <!-- Ward Staff -->
<div class="card"> <div class="panel panel-inverse" data-sortable-id="index-6">
<div class="card-header"> <div class="panel-heading">
<h5 class="mb-0"> <h4 class="panel-title">
<i class="fas fa-user-nurse me-2"></i>Ward Staff <i class="fas fa-user-nurse me-2"></i>Ward Staff
</h5> </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>
<div class="card-body"> <div class="panel-body">
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
{% if ward.nurse_manager %} {% if ward.nurse_manager %}
<div class="list-group-item d-flex justify-content-between align-items-center px-0"> <div class="list-group-item d-flex justify-content-between align-items-center px-0">
@ -422,6 +443,19 @@
</div> </div>
</div> </div>
<div class="modal fade" id="transferModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Transfer Patient</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="transferContent">
</div>
</div>
</div>
</div>
<script> <script>
// Ward detail functionality // Ward detail functionality
function refreshBedLayout() { function refreshBedLayout() {
@ -436,7 +470,7 @@ function toggleBedView() {
function showBedDetails(bedId) { function showBedDetails(bedId) {
// Load bed details in modal // Load bed details in modal
fetch(`/inpatients/beds/${bedId}/details/`) fetch(`{% url 'inpatients:bed_details' 0%}`.replace('0', bedId))
.then(response => response.text()) .then(response => response.text())
.then(html => { .then(html => {
document.getElementById('bedDetailsContent').innerHTML = html; document.getElementById('bedDetailsContent').innerHTML = html;
@ -446,6 +480,18 @@ function showBedDetails(bedId) {
console.error('Error loading bed details:', error); console.error('Error loading bed details:', error);
}); });
} }
function showTransfer(bedId) {
// Load bed details in modal
fetch(`{% url 'inpatients:transfer_patient' 0%}`.replace('0', bedId))
.then(response => response.text())
.then(html => {
document.getElementById('transferContent').innerHTML = html;
new bootstrap.Modal(document.getElementById('transferModal')).show();
})
.catch(error => {
console.error('Error loading transfer details:', error);
});
}
function printWardCensus() { function printWardCensus() {
window.print(); window.print();

View File

@ -83,12 +83,12 @@
class="btn btn-outline-primary btn-sm"> class="btn btn-outline-primary btn-sm">
<i class="fas fa-bed me-1"></i>Beds <i class="fas fa-bed me-1"></i>Beds
</a> </a>
<button class="btn btn-outline-info btn-sm"> <a href="{% url 'inpatients:ward_detail' ward.id %}" class="btn btn-outline-info btn-sm">
<i class="fas fa-eye me-1"></i>View <i class="fas fa-eye me-1"></i>View
</button> </a>
<button class="btn btn-outline-secondary btn-sm"> <a href="{% url 'inpatients:ward_update' ward.id %}" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-edit me-1"></i>Edit <i class="fas fa-edit me-1"></i>Edit
</button> </a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,6 +13,8 @@ urlpatterns = [
# wards # wards
path('wards/', views.WardListView.as_view(), name='ward_list'), path('wards/', views.WardListView.as_view(), name='ward_list'),
path('wards/<int:pk>/', views.WardDetailView.as_view(), name='ward_detail'), path('wards/<int:pk>/', views.WardDetailView.as_view(), name='ward_detail'),
path('wards/create/', views.WardCreateView.as_view(), name='ward_create'),
path('wards/<int:pk>/edit/', views.WardUpdateView.as_view(), name='ward_update'),
path('wards/stats/', views.ward_stats, name='ward_stats'), path('wards/stats/', views.ward_stats, name='ward_stats'),
# Admissions # Admissions
@ -30,11 +32,12 @@ urlpatterns = [
path('transfer/<int:admission_id>/', views.transfer_patient, name='transfer_patient'), path('transfer/<int:admission_id>/', views.transfer_patient, name='transfer_patient'),
path('transfers/<int:transfer_id>/approve/', views.approve_transfer, name='approve_transfer'), path('transfers/<int:transfer_id>/approve/', views.approve_transfer, name='approve_transfer'),
path('transfers/<int:transfer_id>/complete/', views.complete_transfer, name='complete_transfer'), path('transfers/<int:transfer_id>/complete/', views.complete_transfer, name='complete_transfer'),
path('transfers/<int:transfer_id>/cancel/', views.cancel_transfer, name='cancel_transfer'),
path('transfers/<int:transfer_id>/reject/', views.reject_transfer, name='reject_transfer'),
# Beds
# Beds
path('beds/', views.BedManagementView.as_view(), name='bed_management'), path('beds/', views.BedManagementView.as_view(), name='bed_management'),
path('beds/', views.BedListView.as_view(), name='bed_list'), path('beds/list/', views.BedListView.as_view(), name='bed_list'),
path('beds/create/', views.BedCreateView.as_view(), name='bed_create'), path('beds/create/', views.BedCreateView.as_view(), name='bed_create'),
path('beds/<int:pk>/', views.BedDetailView.as_view(), name='bed_detail'), path('beds/<int:pk>/', views.BedDetailView.as_view(), name='bed_detail'),
path('beds/<int:pk>/edit/', views.BedUpdateView.as_view(), name='bed_update'), path('beds/<int:pk>/edit/', views.BedUpdateView.as_view(), name='bed_update'),
@ -45,7 +48,8 @@ urlpatterns = [
path('beds/<int:pk>/unblock/', views.unblock_bed, name='unblock_bed'), path('beds/<int:pk>/unblock/', views.unblock_bed, name='unblock_bed'),
path('beds/<int:pk>/maintenance/', views.maintenance_bed, name='maintenance_bed'), path('beds/<int:pk>/maintenance/', views.maintenance_bed, name='maintenance_bed'),
path('beds/grid/', views.bed_grid, name='bed_grid'), path('beds/grid/', views.bed_grid, name='bed_grid'),
path('beds/<int:bed_id>/status/', views.update_bed_status, name='update_bed_status'), path('beds/status/', views.bed_status_board, name='update_bed_status'),
path('beds/<int:bed_id>/details/', views.bed_details, name='bed_details'),
@ -56,6 +60,10 @@ urlpatterns = [
path('surgery/create/', views.SurgeryScheduleCreateView.as_view(), name='surgery_create'), path('surgery/create/', views.SurgeryScheduleCreateView.as_view(), name='surgery_create'),
path('surgery/calendar/', views.surgery_calendar, name='surgery_calendar'), path('surgery/calendar/', views.surgery_calendar, name='surgery_calendar'),
path('surgery/<int:pk>/cancel/', views.cancel_surgery, name='cancel_surgery'), path('surgery/<int:pk>/cancel/', views.cancel_surgery, name='cancel_surgery'),
path('surgery/<int:pk>/complete/', views.mark_surgery_completed, name='complete_surgery'),
path('surgery/<int:pk>/confirm/', views.confirm_surgery, name='confirm_surgery'),
path('surgery/<int:pk>/prep/', views.prep_surgery, name='prep_surgery'),
path('surgery/<int:pk>/postpone/', views.postpone_surgery, name='postpone_surgery'),
# Actions # Actions

View File

@ -1,6 +1,7 @@
""" """
Views for inpatients app. Views for inpatients app.
""" """
from django.conf.locale import te from django.conf.locale import te
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
@ -172,13 +173,13 @@ class WardListView(LoginRequiredMixin, ListView):
context['buildings'] = Ward.objects.filter( context['buildings'] = Ward.objects.filter(
tenant=tenant tenant=tenant
).exclude(building__isnull=True).exclude(building='').values_list( ).values_list(
'building', flat=True 'building', flat=True
).distinct() ).distinct()
context['floors'] = Ward.objects.filter( context['floors'] = Ward.objects.filter(
tenant=tenant tenant=tenant
).exclude(floor__isnull=True).exclude(floor='').values_list( ).values_list(
'floor', flat=True 'floor', flat=True
).distinct() ).distinct()
@ -215,7 +216,7 @@ class WardDetailView(LoginRequiredMixin, DetailView):
context['beds'] = Bed.objects.filter( context['beds'] = Bed.objects.filter(
ward=ward ward=ward
).select_related( ).select_related(
'current_patient', 'current_admission' 'current_admission'
).order_by('room_number', 'bed_number') ).order_by('room_number', 'bed_number')
# Group beds by room for display # Group beds by room for display
@ -234,6 +235,7 @@ class WardDetailView(LoginRequiredMixin, DetailView):
context['maintenance_beds'] = context['beds'].filter( context['maintenance_beds'] = context['beds'].filter(
status__in=['MAINTENANCE', 'OUT_OF_ORDER', 'CLEANING'] status__in=['MAINTENANCE', 'OUT_OF_ORDER', 'CLEANING']
).count() ).count()
context['reserved_beds'] = context['beds'].filter(status='RESERVED').count()
if context['total_beds'] > 0: if context['total_beds'] > 0:
context['occupancy_rate'] = (context['occupied_beds'] / context['total_beds']) * 100 context['occupancy_rate'] = (context['occupied_beds'] / context['total_beds']) * 100
@ -339,7 +341,7 @@ class BedListView(LoginRequiredMixin, ListView):
"""Filter beds by tenant and search query.""" """Filter beds by tenant and search query."""
queryset = Bed.objects.filter( queryset = Bed.objects.filter(
ward__tenant=self.request.user.tenant ward__tenant=self.request.user.tenant
).select_related('ward', 'current_patient') ).select_related('ward', 'current_admission')
# Handle search query # Handle search query
search_query = self.request.GET.get('search', '') search_query = self.request.GET.get('search', '')
@ -394,13 +396,13 @@ class BedListView(LoginRequiredMixin, ListView):
).order_by('name') ).order_by('name')
# Get bed types for filter dropdown # Get bed types for filter dropdown
context['bed_types'] = Bed.BED_TYPE_CHOICES context['bed_types'] = Bed.BedType.choices
# Get room types for filter dropdown # Get room types for filter dropdown
context['room_types'] = Bed.ROOM_TYPE_CHOICES context['room_types'] = Bed.RoomType.choices
# Get statuses for filter dropdown # Get statuses for filter dropdown
context['statuses'] = Bed.STATUS_CHOICES context['statuses'] = Bed.BedStatus.choices
# Add search query to context # Add search query to context
context['search_query'] = self.request.GET.get('search', '') context['search_query'] = self.request.GET.get('search', '')
@ -426,7 +428,7 @@ class BedDetailView(LoginRequiredMixin, DetailView):
return Bed.objects.filter( return Bed.objects.filter(
ward__tenant=self.request.user.tenant ward__tenant=self.request.user.tenant
).select_related( ).select_related(
'ward', 'current_patient', 'current_admission', 'ward', 'current_admission',
'cleaned_by', 'blocked_by', 'created_by' 'cleaned_by', 'blocked_by', 'created_by'
) )
@ -436,13 +438,12 @@ class BedDetailView(LoginRequiredMixin, DetailView):
# Get bed history - admissions that used this bed # Get bed history - admissions that used this bed
context['admission_history'] = Admission.objects.filter( context['admission_history'] = Admission.objects.filter(
Q(current_bed=bed) | Q(current_bed=bed) current_bed=bed).select_related(
).select_related( 'patient', 'admitting_physician',
'patient', 'admitting_physician' ).order_by('-admission_datetime')
).order_by('-admission_datetime')[:10]
# Get maintenance history if available
# This would require a model to track maintenance events # context['maintenance_history'] = bed.maintenance_history.all().order_by('-scheduled_date')[:10]
return context return context
@ -562,7 +563,7 @@ class BedManagementView(LoginRequiredMixin, ListView):
) )
return queryset.select_related( return queryset.select_related(
'ward', 'current_patient', 'current_admission' 'ward', 'current_admission'
).order_by('ward__name', 'room_number', 'bed_number') ).order_by('ward__name', 'room_number', 'bed_number')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -583,8 +584,8 @@ class BedManagementView(LoginRequiredMixin, ListView):
) )
context.update({ context.update({
'wards': Ward.objects.filter(tenant=tenant, is_active=True), 'wards': Ward.objects.filter(tenant=tenant, is_active=True),
'bed_statuses': Bed.STATUS_CHOICES, 'bed_statuses': Bed.BedStatus.choices,
'bed_types': Bed.BED_TYPE_CHOICES, 'bed_types': Bed.BedType.choices,
'total_beds': Bed.objects.filter(ward__tenant=tenant).count(), 'total_beds': Bed.objects.filter(ward__tenant=tenant).count(),
'available_beds': Bed.objects.filter(ward__tenant=tenant, status='AVAILABLE').count(), 'available_beds': Bed.objects.filter(ward__tenant=tenant, status='AVAILABLE').count(),
'occupied_beds': Bed.objects.filter(ward__tenant=tenant, status='OCCUPIED').count(), 'occupied_beds': Bed.objects.filter(ward__tenant=tenant, status='OCCUPIED').count(),
@ -642,8 +643,8 @@ class AdmissionListView(LoginRequiredMixin, ListView):
total_admissions = Admission.objects.filter(tenant=tenant).count() total_admissions = Admission.objects.filter(tenant=tenant).count()
active_admissions = Admission.objects.filter(tenant=tenant, status='ADMITTED').count() active_admissions = Admission.objects.filter(tenant=tenant, status='ADMITTED').count()
context.update({ context.update({
'admission_statuses': Admission.STATUS_CHOICES, 'admission_statuses': Admission.AdmissionStatus.choices,
'admission_types': Admission.ADMISSION_TYPE_CHOICES, 'admission_types': Admission.AdmissionType.choices,
'wards': Ward.objects.filter(tenant=tenant, is_active=True), 'wards': Ward.objects.filter(tenant=tenant, is_active=True),
'total_admissions': total_admissions, 'total_admissions': total_admissions,
'active_admissions': active_admissions, 'active_admissions': active_admissions,
@ -880,7 +881,7 @@ class SurgeryScheduleListView(LoginRequiredMixin, ListView):
model = SurgerySchedule model = SurgerySchedule
template_name = 'inpatients/surgeries/surgery_schedule.html' template_name = 'inpatients/surgeries/surgery_schedule.html'
context_object_name = 'surgeries' context_object_name = 'surgeries'
paginate_by = 20 paginate_by = 5
def get_queryset(self): def get_queryset(self):
"""Filter surgeries by tenant and search query.""" """Filter surgeries by tenant and search query."""
@ -959,10 +960,10 @@ class SurgeryScheduleListView(LoginRequiredMixin, ListView):
is_active=True, is_active=True,
employee_profile__role__in=['SURGEON', 'PHYSICIAN_ASSISTANT'] employee_profile__role__in=['SURGEON', 'PHYSICIAN_ASSISTANT']
).order_by('last_name', 'first_name') ).order_by('last_name', 'first_name')
from operating_theatre.models import OperatingRoom
# Get operating rooms for filter dropdown # Get operating rooms for filter dropdown
try: try:
from operating_theatre.models import OperatingRoom
context['operating_rooms'] = OperatingRoom.objects.filter( context['operating_rooms'] = OperatingRoom.objects.filter(
tenant=tenant, tenant=tenant,
is_active=True is_active=True
@ -1102,11 +1103,11 @@ def bed_grid(request):
beds = Bed.objects.filter( beds = Bed.objects.filter(
ward_id=ward_id, ward_id=ward_id,
ward__tenant=request.user.tenant ward__tenant=request.user.tenant
).select_related('current_patient', 'current_admission').order_by('room_number', 'bed_number') ).select_related( 'current_admission').order_by('room_number', 'bed_number')
else: else:
beds = Bed.objects.filter( beds = Bed.objects.filter(
ward__tenant=request.user.tenant ward__tenant=request.user.tenant
).select_related('ward', 'current_patient', 'current_admission').order_by( ).select_related('ward', 'current_admission').order_by(
'ward__name', 'room_number', 'bed_number' 'ward__name', 'room_number', 'bed_number'
) )
@ -1271,7 +1272,7 @@ def transfer_patient(request, admission_id):
'priorities': Transfer._meta.get_field('priority').choices 'priorities': Transfer._meta.get_field('priority').choices
} }
return render(request, 'inpatients/patient_transfer.html', context) return render(request, 'inpatients/transfers/transfer_form.html', context)
@login_required @login_required
@ -1279,7 +1280,6 @@ def approve_transfer(request, transfer_id):
""" """
HTMX endpoint for approving a transfer. HTMX endpoint for approving a transfer.
""" """
print("transfer clicked")
transfer = get_object_or_404(Transfer, id=transfer_id, admission__tenant=request.user.tenant) transfer = get_object_or_404(Transfer, id=transfer_id, admission__tenant=request.user.tenant)
if request.method == 'POST': if request.method == 'POST':
@ -1303,7 +1303,7 @@ def approve_transfer(request, transfer_id):
# Log the action # Log the action
AuditLogger.log_event( AuditLogger.log_event(
actor=request.user, user=request.user,
action='TRANSFER_APPROVED', action='TRANSFER_APPROVED',
target=transfer, target=transfer,
target_repr=str(transfer), target_repr=str(transfer),
@ -1321,6 +1321,72 @@ def approve_transfer(request, transfer_id):
return render(request, 'inpatients/partials/approve_transfer_form.html', context) return render(request, 'inpatients/partials/approve_transfer_form.html', context)
@login_required
def cancel_transfer(request, transfer_id):
"""
Cancel a transfer.
"""
transfer = get_object_or_404(
Transfer,
pk=transfer_id,
admission__tenant=request.user.tenant
)
# Cannot cancel completed or already cancelled transfers
if transfer.status in ['COMPLETED', 'CANCELLED']:
messages.error(request, _('Cannot cancel a completed or already cancelled transfer'))
return redirect('inpatients:transfer_management')
if request.method == 'POST':
reason = request.POST.get('reason')
transfer.status = 'CANCELLED'
if reason:
transfer.notes = (
transfer.notes or "") + f"\n\nCancellation Reason ({timezone.now().strftime('%Y-%m-%d %H:%M')}):\n{reason}"
transfer.save()
messages.success(request, _('Transfer cancelled successfully'))
return redirect('inpatients:transfer_management')
return render(request, 'inpatients/transfers/cancel_transfer.html', {
'transfer': transfer
})
@login_required
def reject_transfer(request, transfer_id):
"""
Cancel a transfer.
"""
transfer = get_object_or_404(
Transfer,
pk=transfer_id,
admission__tenant=request.user.tenant
)
# Cannot cancel completed or already cancelled transfers
if transfer.status in ['COMPLETED', 'CANCELLED']:
messages.error(request, _('Cannot cancel a completed or already cancelled transfer'))
return redirect('inpatients:transfer_management')
if request.method == 'POST':
reason = request.POST.get('reason')
transfer.status = 'REJECTED'
if reason:
transfer.notes = (
transfer.notes or "") + f"\n\nRejection Reason ({timezone.now().strftime('%Y-%m-%d %H:%M')}):\n{reason}"
transfer.save()
messages.success(request, _('Transfer rejected successfully'))
return redirect('inpatients:transfer_management')
return render(request, 'inpatients/transfers/cancel_transfer.html', {
'transfer': transfer
})
@login_required @login_required
def complete_transfer(request, transfer_id): def complete_transfer(request, transfer_id):
""" """
@ -1517,11 +1583,19 @@ def bed_status_board(request):
'occupied_beds': beds.filter(status='OCCUPIED').count() 'occupied_beds': beds.filter(status='OCCUPIED').count()
}) })
return render(request, 'inpatients/bed_status_board.html', { return render(request, 'inpatients/partials/update_bed_status_form.html', {
'ward_data': ward_data 'ward_data': ward_data
}) })
@login_required
def bed_details(request, bed_id):
tenant = request.user.tenant
bed = get_object_or_404(Bed, ward__tenant=tenant,id=bed_id)
return render(request, 'inpatients/partials/bed_details.html', {'object':bed})
@login_required @login_required
def clean_bed(request, pk): def clean_bed(request, pk):
""" """
@ -2261,8 +2335,7 @@ class AdmissionDetailView(LoginRequiredMixin, DetailView):
context['has_discharge_summary'] = hasattr(admission, 'discharge_summary') context['has_discharge_summary'] = hasattr(admission, 'discharge_summary')
return context return context
#
#
@ -2541,38 +2614,7 @@ class TransferUpdateView(LoginRequiredMixin, UpdateView):
return reverse('inpatients:transfer_detail', kwargs={'pk': self.object.pk}) return reverse('inpatients:transfer_detail', kwargs={'pk': self.object.pk})
@login_required
# @permission_required('inpatients.change_transfer')
def cancel_transfer(request, pk):
"""
Cancel a transfer.
"""
transfer = get_object_or_404(
Transfer,
pk=pk,
admission__tenant=request.user.tenant
)
# Cannot cancel completed or already cancelled transfers
if transfer.status in ['COMPLETED', 'CANCELLED']:
messages.error(request, _('Cannot cancel a completed or already cancelled transfer'))
return redirect('inpatients:transfer_detail', pk=transfer.pk)
if request.method == 'POST':
reason = request.POST.get('reason')
transfer.status = 'CANCELLED'
if reason:
transfer.notes = (
transfer.notes or "") + f"\n\nCancellation Reason ({timezone.now().strftime('%Y-%m-%d %H:%M')}):\n{reason}"
transfer.save()
messages.success(request, _('Transfer cancelled successfully'))
return redirect('inpatients:transfer_detail', pk=transfer.pk)
return render(request, 'inpatients/transfers/cancel_transfer.html', {
'transfer': transfer
})
@login_required @login_required
@ -2676,6 +2718,109 @@ def cancel_surgery(request, pk):
'surgery': surgery 'surgery': surgery
}) })
@login_required
# @permission_required('inpatients.change_surgeryschedule')
def confirm_surgery(request, pk):
"""
Confirm a surgery.
"""
surgery = get_object_or_404(
SurgerySchedule,
pk=pk,
admission__tenant=request.user.tenant
)
# Cannot cancel completed or already cancelled surgeries
if surgery.status in ['CONFIRMED','IN_PROGRESS' ]:
messages.error(request, _('Cannot confirm a confirmed or already in progress surgery'))
return redirect('inpatients:surgery_detail', pk=surgery.pk)
if request.method == 'POST':
reason = request.POST.get('reason')
surgery.status = 'CONFIRMED'
# if reason:
# surgery.notes = (surgery.notes or "") + f"\n\nCancellation Reason ({timezone.now().strftime('%Y-%m-%d %H:%M')}):\n{reason}"
surgery.save()
messages.success(request, _('Surgery confirmed successfully'))
return redirect('inpatients:surgery_list')
return render(request, 'inpatients/surgeries/surgery_schedule.html', {
'surgery': surgery
})
@login_required
# @permission_required('inpatients.change_surgeryschedule')
def prep_surgery(request, pk):
"""
Prep a surgery.
"""
surgery = get_object_or_404(
SurgerySchedule,
pk=pk,
admission__tenant=request.user.tenant
)
# Cannot cancel completed or already cancelled surgeries
if surgery.status in ['PREP','IN_PROGRESS' ]:
messages.error(request, _('Cannot prep already in progress surgery'))
return redirect('inpatients:surgery_detail', pk=surgery.pk)
if request.method == 'POST':
reason = request.POST.get('reason')
surgery.status = 'PREP'
# if reason:
# surgery.notes = (surgery.notes or "") + f"\n\nCancellation Reason ({timezone.now().strftime('%Y-%m-%d %H:%M')}):\n{reason}"
surgery.save()
messages.success(request, _('Surgery marked as prep successfully'))
return redirect('inpatients:surgery_list')
return render(request, 'inpatients/surgeries/surgery_schedule.html', {
'surgery': surgery
})
@login_required
# @permission_required('inpatients.change_surgeryschedule')
def postpone_surgery(request, pk):
"""
Postpone a surgery.
"""
surgery = get_object_or_404(
SurgerySchedule,
pk=pk,
admission__tenant=request.user.tenant
)
# Cannot cancel completed or already cancelled surgeries
if surgery.status in ['COMPLETED','CANCELLED' ]:
messages.error(request, _('Cannot postpone completed or cancelled surgery'))
return redirect('inpatients:surgery_detail', pk=surgery.pk)
if request.method == 'POST':
reason = request.POST.get('reason')
surgery.status = 'POSTPONED'
if reason:
surgery.notes = (surgery.notes or "") + f"\n\nPostpone Reason ({timezone.now().strftime('%Y-%m-%d %H:%M')}):\n{reason}"
surgery.save()
messages.success(request, _('Surgery postponed successfully'))
return redirect('inpatients:surgery_list')
return render(request, 'inpatients/surgeries/surgery_schedule.html', {
'surgery': surgery
})
@login_required @login_required
def inpatient_stats(request): def inpatient_stats(request):
""" """
@ -2775,3 +2920,6 @@ def inpatient_stats(request):
'date_to': date_to 'date_to': date_to
}) })
# class DischargeManagement(LoginRequiredMixin, ListView):
# model = DischargeSummary

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -736,47 +736,34 @@ class OperatingTheatreDataGenerator:
# Arabic-influenced room names # Arabic-influenced room names
self.room_names = [ self.room_names = [
"غرفة العمليات الأولى", "غرفة العمليات الثانية", "غرفة العمليات الثالثة", "First Operating Room", "Second Operating Room", "Third Operating Room",
"جناح الجراحة العامة", "جناح جراحة القلب", "جناح جراحة الأعصاب", "General Surgery Wing", "Cardiac Surgery Wing", "Neurosurgery Wing",
"وحدة الجراحة الطارئة", "مركز الجراحة الروبوتية", "قسم جراحة العظام" "Emergency Surgery Unit", "Robotic Surgery Center", "Orthopedic Surgery Department"
] ]
# Common Saudi procedures (Arabic + English) # Common Saudi procedures (Arabic + English)
self.procedures = [ self.procedures = [
"استئصال المرارة بالمنظار", "Laparoscopic Cholecystectomy", "Laparoscopic Cholecystectomy","Open Heart Surgery",
"جراحة القلب المفتوح", "Open Heart Surgery", "Total Knee Replacement", "Inguinal Hernia Repair",
"استبدال مفصل الركبة", "Total Knee Replacement", "Appendectomy", "Diagnostic Laparoscopy",
"جراحة الفتق الإربي", "Inguinal Hernia Repair", "Thyroidectomy", "Cataract Surgery",
"استئصال الزائدة الدودية", "Appendectomy", "Tonsillectomy", "Hemorrhoidectomy"
"جراحة المنظار التشخيصي", "Diagnostic Laparoscopy",
"استئصال الغدة الدرقية", "Thyroidectomy",
"جراحة الساد", "Cataract Surgery",
"استئصال اللوزتين", "Tonsillectomy",
"جراحة البواسير", "Hemorrhoidectomy"
] ]
# Saudi medical equipment (common in Saudi hospitals) # Saudi medical equipment (common in Saudi hospitals)
self.equipment = [ self.equipment = [
"منظار البطن كارل زايس", "Carl Zeiss Laparoscope", "Carl Zeiss Laparoscope","Electrocautery Unit",
"جهاز القطع الكهربائي", "Electrocautery Unit", "Drager Anesthesia Machine", "Leica Surgical Microscope",
"جهاز التخدير دريجر", "Drager Anesthesia Machine", "da Vinci Surgical Robot", "Portable CT Scanner",
"مجهر جراحي ليكا", "Leica Surgical Microscope", "Cystoscope", "Heart-Lung Machine"
"روبوت دافنشي", "da Vinci Surgical Robot",
"جهاز الأشعة المقطعية المحمول", "Portable CT Scanner",
"منظار المثانة", "Cystoscope",
"مضخة القلب الرئة", "Heart-Lung Machine"
] ]
# Common diagnoses in Arabic and English # Common diagnoses in Arabic and English
self.diagnoses = [ self.diagnoses = [
"التهاب المرارة الحاد", "Acute Cholecystitis", "Acute Cholecystitis", "Bowel Obstruction",
"انسداد الأمعاء", "Bowel Obstruction", "Femur Fracture", "Thyroid Nodules",
"كسر في عظم الفخذ", "Femur Fracture", "Kidney Stones", "Appendicitis",
"أورام الغدة الدرقية", "Thyroid Nodules", "Inguinal Hernia", "Colorectal Cancer"
"حصى الكلى", "Kidney Stones",
"التهاب الزائدة الدودية", "Appendicitis",
"الفتق الإربي", "Inguinal Hernia",
"سرطان القولون", "Colorectal Cancer"
] ]
def get_existing_data(self): def get_existing_data(self):
@ -795,15 +782,15 @@ class OperatingTheatreDataGenerator:
# Try to categorize users by role/specialty # Try to categorize users by role/specialty
surgeons = list(User.objects.filter( surgeons = list(User.objects.filter(
groups__name__icontains='surgeon' groups__name__icontains='SURGEON'
).distinct()) or users[:max(1, len(users) // 3)] ).distinct()) or users[:max(1, len(users) // 3)]
anesthesiologists = list(User.objects.filter( anesthesiologists = list(User.objects.filter(
groups__name__icontains='anesthesia' groups__name__icontains='ANESTHESIA'
).distinct()) or users[len(users) // 3:2 * len(users) // 3] ).distinct()) or users[len(users) // 3:2 * len(users) // 3]
nurses = list(User.objects.filter( nurses = list(User.objects.filter(
groups__name__icontains='nurse' groups__name__icontains='NURSE'
).distinct()) or users[2 * len(users) // 3:] ).distinct()) or users[2 * len(users) // 3:]
# Get patients # Get patients

View File

@ -224,7 +224,7 @@ class InsuranceInfoAdmin(admin.ModelAdmin):
('Subscriber Information', { ('Subscriber Information', {
'fields': ( 'fields': (
'subscriber_name', 'subscriber_relationship', 'subscriber_name', 'subscriber_relationship',
'subscriber_dob', 'subscriber_ssn' 'subscriber_dob',
) )
}), }),
('Coverage', { ('Coverage', {

BIN
static/.DS_Store vendored

Binary file not shown.

BIN
static/css/.DS_Store vendored Normal file

Binary file not shown.

BIN
static/data/.DS_Store vendored Normal file

Binary file not shown.

BIN
static/img/.DS_Store vendored

Binary file not shown.

BIN
static/plugins/.DS_Store vendored Normal file

Binary file not shown.

BIN
static/plugins/ionicons/dist/.DS_Store vendored Normal file

Binary file not shown.