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>
<!-- Problems Table -->
<div class="card">
<div class="card border border-primary-subtle">
<div class="table-responsive">
<table class="table table-sm table-hover mb-0 align-middle">
<thead class="table-primary">
<thead class="table-primary ">
<tr>
<th>Patient</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:
model = Room
fields = [
'floor', 'room_number', 'name', 'room_type', 'area_sqm',
'capacity', 'occupancy_status', 'is_accessible', 'current_tenant',
'lease_start_date', 'lease_end_date', 'monthly_rent',
'has_hvac', 'has_electrical', 'has_plumbing', 'has_internet', 'notes'
'floor', 'room_number', 'name', 'area_sqm',
'capacity', 'occupancy_status', 'is_accessible', 'notes'
]
widgets = {
'floor': forms.Select(attrs={'class': 'form-select'}),
@ -81,26 +79,13 @@ class RoomForm(forms.ModelForm):
'room_type': forms.Select(attrs={'class': 'form-select'}),
'area_sqm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'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'}),
'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}),
}
def clean(self):
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
@ -132,7 +117,7 @@ class AssetForm(forms.ModelForm):
class Meta:
model = Asset
fields = [
'asset_id', 'name', 'category', 'building', 'floor', 'room',
'name', 'category', 'building', 'floor', 'room',
'location_description', 'manufacturer', 'model', 'serial_number',
'purchase_date', 'purchase_cost', 'current_value', 'depreciation_rate',
'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'
]
widgets = {
'asset_id': forms.TextInput(attrs={'class': 'form-control'}),
'name': forms.TextInput(attrs={'class': 'form-control'}),
'category': forms.Select(attrs={'class': 'form-select'}),
'building': forms.Select(attrs={'class': 'form-select'}),
@ -271,7 +255,7 @@ class MaintenanceScheduleForm(forms.ModelForm):
model = MaintenanceSchedule
fields = [
'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'
]
widgets = {
@ -322,8 +306,7 @@ class VendorForm(forms.ModelForm):
model = Vendor
fields = [
'name', 'vendor_type', 'contact_person', 'email', 'phone',
'address', 'license_number', 'insurance_policy',
'insurance_expiry', 'rating', 'is_active'
'address', 'crn', 'vrn', 'rating', 'is_active'
]
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
@ -332,9 +315,8 @@ class VendorForm(forms.ModelForm):
'email': forms.EmailInput(attrs={'class': 'form-control'}),
'phone': forms.TextInput(attrs={'class': 'form-control'}),
'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'license_number': forms.TextInput(attrs={'class': 'form-control'}),
'insurance_policy': forms.TextInput(attrs={'class': 'form-control'}),
'insurance_expiry': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'crn': forms.TextInput(attrs={'class': 'form-control'}),
'vrn': forms.TextInput(attrs={'class': 'form-control'}),
'rating': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0', 'max': '5'}),
'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
occupied_rooms = Room.objects.filter(
floor__building=self,
occupancy_status='occupied'
occupancy_status=Room.OccupancyStatus.OCCUPIED
).count()
return (occupied_rooms / total_rooms) * 100

View File

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

View File

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

View File

@ -29,7 +29,7 @@ class FacilityDashboardView(LoginRequiredMixin, TemplateView):
context['stats'] = {
'total_buildings': Building.objects.filter(is_active=True).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(),
'open_work_orders': MaintenanceRequest.objects.filter(status__in=['submitted', 'assigned', 'in_progress']).count(),
'completed_today': MaintenanceRequest.objects.filter(completed_date__date=today).count(),
@ -90,7 +90,7 @@ class BuildingListView(LoginRequiredMixin, ListView):
def get_queryset(self):
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 = self.request.GET.get('search')
@ -107,16 +107,16 @@ class BuildingListView(LoginRequiredMixin, ListView):
queryset = queryset.filter(building_type=building_type)
# Filter by airport
airport = self.request.GET.get('airport')
if airport:
queryset = queryset.filter(airport_code=airport)
# airport = self.request.GET.get('airport')
# if airport:
# queryset = queryset.filter(airport_code=airport)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
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

View File

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

Binary file not shown.

Binary file not shown.

View File

@ -90,72 +90,120 @@
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Page Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1">
<i class="fas fa-users me-2"></i>Human Resources Dashboard
</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<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()">
<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>Human<span class="fw-light">Resources</span>
</h1>
<p class="text-muted">Manage all HR from one place.</p>
</div>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="refreshDashboard()">
<i class="fas fa-sync-alt me-2"></i>Refresh
</button>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-cog me-2"></i>Quick Actions
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'hr:employee_create' %}">
<i class="fas fa-user-plus me-2"></i>Add Employee
</a></li>
<li><a class="dropdown-item" href="{% url 'hr:department_create' %}">
<i class="fas fa-building me-2"></i>Add Department
</a></li>
<li><a class="dropdown-item" href="{% url 'hr:schedule_create' %}">
<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>
<a class="btn btn-sm btn-outline-primary" href="{% url 'hr:employee_create' %}">
<i class="fas fa-user-plus me-2"></i>Add Employee
</a>
<a class="btn btn-sm btn-outline-secondary" href="{% url 'hr:department_create' %}">
<i class="fas fa-building me-2"></i>Add Department
</a>
<a class="btn btn-sm btn-outline-theme" href="{% url 'hr:schedule_create' %}">
<i class="fas fa-calendar-plus me-2"></i>Create Schedule
</a>
<a class="btn btn-sm btn-outline-warning" href="#">
<i class="fas fa-chart-bar me-2"></i>Generate Report
</a>
</div>
</div>
</div>
<div class="container-fluid">
<!-- Statistics Cards -->
<div class="row mb-4" id="hr-stats">
{% include "hr/partials/hr_stats.html" %}
<div class="row mb-2">
<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 class="row">
<!-- Recent Activity -->
<div class="col-lg-8">
<div class="card mb-4">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-clock me-2"></i>Recent Activity
</h5>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary" onclick="refreshActivity()">
<i class="fas fa-sync-alt"></i>
</button>
<a href="#" class="btn btn-outline-primary">
<i class="fas fa-list me-1"></i>View All
</a>
</div>
<div class="panel panel-inverse" data-sortable-id="index-1">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-clock me-2"></i>Recent Activity
</h4>
<div class="panel-heading-btn">
<button type="button" class="btn btn-xs btn-outline-secondary me-2" onclick="refreshActivity()">
<i class="fas fa-sync-alt"></i>
</button>
<a href="#" class="btn btn-xs btn-outline-primary me-2">
<i class="fas fa-list me-1"></i>View All
</a>
<a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
</div>
</div>
<div class="card-body">
<div class="panel-body">
<div class="timeline" id="activity-timeline">
<!-- Recent Hires -->
{% for employee in recent_hires %}
@ -217,13 +265,19 @@
</div>
<!-- Attendance Summary -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<div class="panel panel-inverse" data-sortable-id="index-2">
<div class="panel-heading">
<h4 class="panel-title">
<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 class="card-body">
<div class="panel-body">
{% include "hr/partials/attendance_summary.html" %}
</div>
</div>
@ -232,13 +286,19 @@
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<div class="panel panel-inverse" data-sortable-id="index-3">
<div class="panel-heading">
<h4 class="panel-title">
<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 class="panel-body">
<div class="d-grid gap-2">
<a href="{% url 'hr:employee_create' %}" class="btn btn-primary">
<i class="fas fa-user-plus me-2"></i>Add Employee
@ -266,13 +326,22 @@
</div>
<!-- Department Overview -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-building me-2"></i>Department Overview
</h5>
<div class="panel panel-inverse" data-sortable-id="index-4">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-building me-2"></i>Department Overview
</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 class="card-body">
<div class="panel-body">
<div class="list-group list-group-flush">
{% for dept in departments %}
<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">
<i class="fas fa-building fa-2x text-muted mb-2"></i>
<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>
{% endfor %}
</div>
@ -296,13 +363,19 @@
</div>
<!-- Pending Tasks -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-tasks me-2"></i>Pending Tasks
</h5>
<div class="panel panel-inverse" data-sortable-id="index-5">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-tasks me-2"></i>Pending Tasks
</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 class="panel-body">
<div class="list-group list-group-flush">
{% 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">
@ -383,51 +456,51 @@ function refreshActivity() {
}
// Clock in/out functionality
function clockIn() {
fetch('{% url "hr:clock_in" %}', {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Clocked in successfully!');
refreshDashboard();
} else {
alert('Error clocking in: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error clocking in');
});
}
function clockOut() {
fetch('{% url "hr:clock_out" %}', {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Clocked out successfully!');
refreshDashboard();
} else {
alert('Error clocking out: ' + data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('Error clocking out');
});
}
{#function clockIn() {#}
{# fetch('{% url "hr:clock_in" employee_id%}', {#}
{# method: 'POST',#}
{# headers: {#}
{# 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,#}
{# 'Content-Type': 'application/json',#}
{# },#}
{# })#}
{# .then(response => response.json())#}
{# .then(data => {#}
{# if (data.success) {#}
{# alert('Clocked in successfully!');#}
{# refreshDashboard();#}
{# } else {#}
{# alert('Error clocking in: ' + data.message);#}
{# }#}
{# })#}
{# .catch(error => {#}
{# console.error('Error:', error);#}
{# alert('Error clocking in');#}
{# });#}
{# }#}
{##}
{#function clockOut() {#}
{# fetch('{% url "hr:clock_out" %}', {#}
{# method: 'POST',#}
{# headers: {#}
{# 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,#}
{# 'Content-Type': 'application/json',#}
{# },#}
{# })#}
{# .then(response => response.json())#}
{# .then(data => {#}
{# if (data.success) {#}
{# alert('Clocked out successfully!');#}
{# refreshDashboard();#}
{# } else {#}
{# alert('Error clocking out: ' + data.message);#}
{# }#}
{# })#}
{# .catch(error => {#}
{# console.error('Error:', error);#}
{# alert('Error clocking out');#}
{# });#}
{# }#}
</script>
{% endblock %}

View File

@ -28,235 +28,240 @@
{% endblock %}
{% 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 -->
<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 -->
<div class="row">
<!-- begin col-12 -->
<div class="col-xl-12">
<!-- begin panel -->
<div class="panel panel-inverse">
<!-- begin panel-heading -->
<div class="panel-heading">
<h4 class="panel-title">Departments</h4>
<div class="panel-heading-btn">
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
</div>
</div>
<!-- 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
<div class="row">
<!-- begin col-12 -->
<div class="col-xl-12">
<!-- begin panel -->
<div class="panel panel-inverse">
<!-- begin panel-heading -->
<div class="panel-heading">
<h4 class="panel-title">Departments</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"></i> Add Department
</a>
<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>
</div>
<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 class="d-flex">
<form method="get" class="me-2">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search departments..." name="search" value="{{ search }}">
<button class="btn btn-outline-secondary" type="submit">
<i class="fas fa-search"></i>
</div>
<!-- end panel-heading -->
<!-- begin panel-body -->
<div class="panel-body">
<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>
</div>
</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 class="d-flex">
<form method="get" class="me-2">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search departments..." name="search" value="{{ search }}">
<button class="btn btn-outline-secondary" type="submit">
<i class="fas fa-search"></i>
</button>
</div>
</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>
<!-- Table View -->
<div id="table-view">
<table id="departments-table" class="table table-striped table-bordered align-middle">
<thead>
<tr>
<th>Department Name</th>
<th>Code</th>
<th>Type</th>
<th>Head</th>
<th>Employees</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for department in departments %}
<tr>
<td>
<strong>{{ department.name }}</strong>
{% if department.parent_department %}
<small class="d-block text-muted">
<i class="fas fa-level-up-alt fa-flip-horizontal"></i> {{ department.parent_department.name }}
</small>
{% endif %}
</td>
<td>{{ department.department_code }}</td>
<td>{{ department.get_department_type_display }}</td>
<td>
{% 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 %}
</td>
<td>
<span class="badge bg-primary">{{ department.employee_count }}</span>
</td>
<td>
<!-- Table View -->
<div id="table-view">
<table id="departments-table" class="table table-striped table-bordered align-middle">
<thead>
<tr>
<th>Department Name</th>
<th>Code</th>
<th>Type</th>
<th>Head</th>
<th>Employees</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for department in departments %}
<tr>
<td>
<strong>{{ department.name }}</strong>
{% if department.parent_department %}
<small class="d-block text-muted">
<i class="fas fa-level-up-alt fa-flip-horizontal"></i> {{ department.parent_department.name }}
</small>
{% endif %}
</td>
<td>{{ department.department_code }}</td>
<td>{{ department.get_department_type_display }}</td>
<td>
{% 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 %}
</td>
<td>
<span class="badge bg-primary">{{ department.employee_count }}</span>
</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 %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-danger">Inactive</span>
{% endif %}
</td>
<td>
<div class="btn-group">
</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>
<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>
<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>
<i class="fas fa-trash"></i> Delete
</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 %}
<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>
{% empty %}
<div class="col-12">
<div class="alert alert-info">
No departments found.
{% empty %}
<div class="col-12">
<div class="alert alert-info">
No departments found.
</div>
</div>
{% endfor %}
<!-- Pagination for Card View -->
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
</div>
{% endfor %}
<!-- Pagination for Card View -->
{% if is_paginated %}
{% include 'partial/pagination.html' %}
{% endif %}
</div>
<!-- end panel-body -->
</div>
<!-- end panel-body -->
<!-- end panel -->
</div>
<!-- end panel -->
<!-- end col-12 -->
</div>
<!-- end col-12 -->
</div>
<!-- end row -->
</div>
{% endblock %}
{% block js %}

View File

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

View File

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

View File

@ -4,112 +4,54 @@
{% block title %}Employee Management - {{ block.super }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Page Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1">
<i class="fas fa-users me-2"></i>Employee Management
</h1>
<nav aria-label="breadcrumb">
<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="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>Employee<span class="fw-light">Management</span>
</h1>
<p class="text-muted">Review employees and add new ones.</p>
</div>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<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">
<a href="{% url 'hr:employee_create' %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-plus me-2"></i>Add Employee
</a>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<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">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 class="btn-group">
<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
</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>
</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 -->
<div class="card mb-4">
<div class="card-header">
<div class="card border border-success mb-4">
<div class="card-header bg-success text-white">
<h5 class="mb-0">
<i class="fas fa-filter me-2"></i>Filters
</h5>
@ -165,8 +107,8 @@
</div>
<!-- Employees List -->
<div class="card">
<div class="card-header">
<div class="card border border-primary">
<div class="card-header bg-primary text-white">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-list me-2"></i>Employees
@ -175,10 +117,10 @@
{% endif %}
</h5>
<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
</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
</button>
</div>
@ -247,7 +189,7 @@
</div>
</td>
<td>
<span class="font-monospace">{{ employee.employee_number }}</span>
<span class="fw-bold">{{ employee.employee_id }}</span>
</td>
<td>
{% if employee.department %}
@ -326,7 +268,8 @@
{% endif %}
</div>
</div>
</div>
</div>
<script>
// Employee list functionality
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>
</td>
<td>{{ entry.date|date:"M d, Y" }}</td>
<td>{{ entry.start_time|time:"H:i" }} - {{ entry.end_time|time:"H:i" }}</td>
<td>{{ entry.hours }} hrs</td>
<td>{{ entry.work_date|date:"M d, Y" }}</td>
<td>{{ entry.clock_in_time|time:"H:i" }} - {{ entry.clock_out_time|time:"H:i" }}</td>
<td>{{ entry.regular_hours }} hrs</td>
<td>
{% if entry.is_overtime %}
<span class="badge bg-danger">Overtime</span>

View File

@ -94,8 +94,9 @@ urlpatterns = [
# ============================================================================
# ACTION URLS FOR WORKFLOW OPERATIONS
# ============================================================================
path('clock-in/', views.clock_in, name='clock_in'),
path('clock-out/', views.clock_out, name='clock_out'),
path('employees/<int:employee_id>/clock-controls/', views.clock_controls, name='clock_controls'),
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('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.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 import messages
from django.views.generic import (
@ -29,6 +31,7 @@ from .forms import (
from core.utils import AuditLogger
class HRDashboardView(LoginRequiredMixin, TemplateView):
"""
Main HR dashboard with comprehensive statistics and recent activity.
@ -45,7 +48,7 @@ class HRDashboardView(LoginRequiredMixin, TemplateView):
tenant=self.request.user.tenant,
employment_status='ACTIVE'
).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(
employee__tenant=self.request.user.tenant,
status='PENDING'
@ -1098,103 +1101,193 @@ def attendance_summary(request):
return render(request, 'hr/partials/attendance_summary.html', context)
@login_required
def clock_in(request):
"""
Clock in an employee.
"""
if request.method == 'POST':
employee_id = request.POST.get('employee_id')
try:
employee = Employee.objects.get(
id=employee_id,
tenant=request.user.tenant
)
# Check if already clocked in today
today = timezone.now().date()
existing_entry = TimeEntry.objects.filter(
employee=employee,
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.'})
@require_GET
def clock_controls(request, employee_id):
"""Return the clock controls partial for today's state (HTMX GET)."""
employee = get_object_or_404(Employee, id=employee_id, tenant=request.user.tenant)
today = timezone.localdate()
# Prefer the open entry for today; otherwise the last entry today (finished)
time_entry = (
TimeEntry.objects
.filter(employee=employee, work_date=today)
.order_by('clock_out_time', '-clock_in_time') # open first (clock_out_time NULL), else latest finished
.first()
)
return render(request, 'hr/partials/clock_controls.html', {
'employee': employee,
'time_entry': time_entry,
})
@login_required
def clock_out(request):
"""
Clock out an employee.
"""
if request.method == 'POST':
employee_id = request.POST.get('employee_id')
try:
employee = Employee.objects.get(
id=employee_id,
tenant=request.user.tenant
)
# Find active time 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:
return JsonResponse({
'success': False,
'message': 'No active clock-in found for this employee.'
})
# Update time entry
time_entry.clock_out_time = timezone.now()
time_entry.save() # This will trigger the save method to calculate hours
return JsonResponse({
'success': True,
'message': f'{employee.get_full_name()} clocked out successfully.',
'time_entry_id': time_entry.id,
'hours_worked': float(time_entry.total_hours)
})
except Employee.DoesNotExist:
return JsonResponse({
'success': False,
'message': 'Employee not found.'
})
return JsonResponse({'success': False, 'message': 'Invalid request.'})
def _render_controls(request, employee, time_entry):
html = render_to_string('hr/partials/clock_controls.html',
{'employee': employee, 'time_entry': time_entry},
request=request)
return HttpResponse(html)
@require_POST
def clock_in(request, employee_id):
employee = get_object_or_404(Employee, id=employee_id, tenant=request.user.tenant)
today = timezone.localdate()
open_entry = TimeEntry.objects.filter(
employee=employee, work_date=today, clock_out_time__isnull=True
).first()
if open_entry:
return _render_controls(request, employee, open_entry)
time_entry = TimeEntry.objects.create(
employee=employee, work_date=today, clock_in_time=timezone.now(), status='DRAFT'
)
if request.headers.get('HX-Request'):
return _render_controls(request, employee, time_entry)
return JsonResponse({'success': True, 'time_entry_id': time_entry.id})
@require_POST
def clock_out(request, employee_id):
employee = get_object_or_404(Employee, id=employee_id, tenant=request.user.tenant)
today = timezone.localdate()
time_entry = TimeEntry.objects.filter(
employee=employee, work_date=today, clock_out_time__isnull=True
).first()
if not time_entry:
# Re-render to default state (will show "Clock In")
if request.headers.get('HX-Request'):
return _render_controls(request, employee, None)
return JsonResponse({'success': False, 'message': 'No active clock-in found.'}, status=400)
time_entry.clock_out_time = timezone.now()
time_entry.save()
if request.headers.get('HX-Request'):
return _render_controls(request, employee, time_entry)
return JsonResponse({'success': True, 'time_entry_id': time_entry.id})
# @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
@ -2644,111 +2737,111 @@ def assign_department_head(request, pk):
# return redirect('hr:time_entry_detail', pk=time_entry.pk)
#
#
# @login_required
# def mark_time_entry_paid(request, pk):
# """
# Mark a time entry as paid.
# """
# time_entry = get_object_or_404(
# TimeEntry,
# pk=pk,
# tenant=request.user.tenant
# )
#
# if request.method == 'POST':
# if not request.user.has_perm('hr.change_timeentry'):
# messages.error(request, "You don't have permission to mark time entries as paid.")
# return redirect('hr:time_entry_detail', pk=time_entry.pk)
#
# time_entry.is_paid = True
# time_entry.payment_date = timezone.now().date()
# time_entry.save()
#
# messages.success(
# request,
# 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}))
# return redirect(redirect_url)
#
# return redirect('hr:time_entry_detail', pk=time_entry.pk)
#
#
# @login_required
# def bulk_approve_time_entries(request):
# """
# Bulk approve time entries.
# """
# if not request.user.has_perm('hr.approve_timeentry'):
# messages.error(request, "You don't have permission to approve time entries.")
# return redirect('hr:time_entry_list')
#
# if request.method == 'POST':
# entry_ids = request.POST.getlist('entry_ids')
#
# if not entry_ids:
# messages.warning(request, "No time entries selected for approval.")
# return redirect('hr:time_entry_list')
#
# # Get entries that belong to this tenant
# entries = TimeEntry.objects.filter(
# tenant=request.user.tenant,
# pk__in=entry_ids,
# is_approved=False
# )
#
# # Update all entries
# updated_count = entries.update(
# is_approved=True,
# approved_by=request.user,
# approval_date=timezone.now()
# )
#
# messages.success(request, f"{updated_count} time entries approved successfully.")
#
# redirect_url = request.POST.get('next', reverse('hr:time_entry_list'))
# return redirect(redirect_url)
#
# return redirect('hr:time_entry_list')
#
#
# @login_required
# def bulk_mark_time_entries_paid(request):
# """
# Bulk mark time entries as paid.
# """
# if not request.user.has_perm('hr.change_timeentry'):
# messages.error(request, "You don't have permission to mark time entries as paid.")
# return redirect('hr:time_entry_list')
#
# if request.method == 'POST':
# entry_ids = request.POST.getlist('entry_ids')
#
# if not entry_ids:
# messages.warning(request, "No time entries selected for payment.")
# return redirect('hr:time_entry_list')
#
# # Get entries that belong to this tenant
# entries = TimeEntry.objects.filter(
# tenant=request.user.tenant,
# pk__in=entry_ids,
# is_approved=True,
# is_paid=False
# )
#
# # Update all entries
# updated_count = entries.update(
# is_paid=True,
# payment_date=timezone.now().date()
# )
#
# messages.success(request, f"{updated_count} time entries marked as paid successfully.")
#
# redirect_url = request.POST.get('next', reverse('hr:time_entry_list'))
# return redirect(redirect_url)
#
# return redirect('hr:time_entry_list')
@login_required
def mark_time_entry_paid(request, pk):
"""
Mark a time entry as paid.
"""
time_entry = get_object_or_404(
TimeEntry,
pk=pk,
tenant=request.user.tenant
)
if request.method == 'POST':
if not request.user.has_perm('hr.change_timeentry'):
messages.error(request, "You don't have permission to mark time entries as paid.")
return redirect('hr:time_entry_detail', pk=time_entry.pk)
time_entry.is_paid = True
time_entry.payment_date = timezone.now().date()
time_entry.save()
messages.success(
request,
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}))
return redirect(redirect_url)
return redirect('hr:time_entry_detail', pk=time_entry.pk)
@login_required
def bulk_approve_time_entries(request):
"""
Bulk approve time entries.
"""
if not request.user.has_perm('hr.approve_timeentry'):
messages.error(request, "You don't have permission to approve time entries.")
return redirect('hr:time_entry_list')
if request.method == 'POST':
entry_ids = request.POST.getlist('entry_ids')
if not entry_ids:
messages.warning(request, "No time entries selected for approval.")
return redirect('hr:time_entry_list')
# Get entries that belong to this tenant
entries = TimeEntry.objects.filter(
tenant=request.user.tenant,
pk__in=entry_ids,
is_approved=False
)
# Update all entries
updated_count = entries.update(
is_approved=True,
approved_by=request.user,
approval_date=timezone.now()
)
messages.success(request, f"{updated_count} time entries approved successfully.")
redirect_url = request.POST.get('next', reverse('hr:time_entry_list'))
return redirect(redirect_url)
return redirect('hr:time_entry_list')
@login_required
def bulk_mark_time_entries_paid(request):
"""
Bulk mark time entries as paid.
"""
if not request.user.has_perm('hr.change_timeentry'):
messages.error(request, "You don't have permission to mark time entries as paid.")
return redirect('hr:time_entry_list')
if request.method == 'POST':
entry_ids = request.POST.getlist('entry_ids')
if not entry_ids:
messages.warning(request, "No time entries selected for payment.")
return redirect('hr:time_entry_list')
# Get entries that belong to this tenant
entries = TimeEntry.objects.filter(
tenant=request.user.tenant,
pk__in=entry_ids,
is_approved=True,
is_paid=False
)
# Update all entries
updated_count = entries.update(
is_paid=True,
payment_date=timezone.now().date()
)
messages.success(request, f"{updated_count} time entries marked as paid successfully.")
redirect_url = request.POST.get('next', reverse('hr:time_entry_list'))
return redirect(redirect_url)
return redirect('hr:time_entry_list')
#
#
# 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})
#
#
# @login_required
# def acknowledge_review(request, pk):
# """
# Acknowledge a performance review.
# """
# review = get_object_or_404(
# PerformanceReview,
# pk=pk,
# tenant=request.user.tenant
# )
#
# # Check if the user is the employee being reviewed
# if hasattr(request.user, 'employee_profile') and request.user.employee_profile == review.employee:
# if request.method == 'POST':
# review.is_acknowledged = True
# review.acknowledgment_date = timezone.now()
# review.save()
#
# messages.success(request, "You have acknowledged this performance review.")
#
# redirect_url = request.POST.get('next', reverse('hr:performance_review_detail', kwargs={'pk': review.pk}))
# return redirect(redirect_url)
# else:
# messages.error(request, "Only the employee being reviewed can acknowledge this review.")
#
# return redirect('hr:performance_review_detail', pk=review.pk)
#
#
# @login_required
# def complete_review(request, pk):
# """
# Mark a performance review as completed.
# """
# review = get_object_or_404(
# PerformanceReview,
# pk=pk,
# tenant=request.user.tenant
# )
#
# if request.method == 'POST':
# if not request.user.has_perm('hr.change_performancereview'):
# messages.error(request, "You don't have permission to complete reviews.")
# return redirect('hr:performance_review_detail', pk=review.pk)
#
# # Check if reviewer is the one completing the review
# if review.reviewer != request.user:
# messages.warning(request, "Only the assigned reviewer should complete this review.")
#
# # Update review status
# review.status = 'COMPLETED'
# review.completion_date = timezone.now().date()
# review.save()
#
# # Update employee's last and next review dates
# employee = review.employee
# employee.last_review_date = review.completion_date
# employee.next_review_date = review.next_review_date
# employee.save()
#
# messages.success(
# request,
# 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}))
# return redirect(redirect_url)
#
# return redirect('hr:performance_review_detail', pk=review.pk)
#
#
@login_required
def acknowledge_review(request, pk):
"""
Acknowledge a performance review.
"""
review = get_object_or_404(
PerformanceReview,
pk=pk,
tenant=request.user.tenant
)
# Check if the user is the employee being reviewed
if hasattr(request.user, 'employee_profile') and request.user.employee_profile == review.employee:
if request.method == 'POST':
review.is_acknowledged = True
review.acknowledgment_date = timezone.now()
review.save()
messages.success(request, "You have acknowledged this performance review.")
redirect_url = request.POST.get('next', reverse('hr:performance_review_detail', kwargs={'pk': review.pk}))
return redirect(redirect_url)
else:
messages.error(request, "Only the employee being reviewed can acknowledge this review.")
return redirect('hr:performance_review_detail', pk=review.pk)
@login_required
def complete_review(request, pk):
"""
Mark a performance review as completed.
"""
review = get_object_or_404(
PerformanceReview,
pk=pk,
tenant=request.user.tenant
)
if request.method == 'POST':
if not request.user.has_perm('hr.change_performancereview'):
messages.error(request, "You don't have permission to complete reviews.")
return redirect('hr:performance_review_detail', pk=review.pk)
# Check if reviewer is the one completing the review
if review.reviewer != request.user:
messages.warning(request, "Only the assigned reviewer should complete this review.")
# Update review status
review.status = 'COMPLETED'
review.completion_date = timezone.now().date()
review.save()
# Update employee's last and next review dates
employee = review.employee
employee.last_review_date = review.completion_date
employee.next_review_date = review.next_review_date
employee.save()
messages.success(
request,
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}))
return redirect(redirect_url)
return redirect('hr:performance_review_detail', pk=review.pk)
# class TrainingRecordListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# """
# List view for training records.
@ -3349,36 +3442,36 @@ def assign_department_head(request, pk):
# return redirect('hr:dashboard')
#
#
# @login_required
# def employee_schedule(request, employee_id):
# """
# View an employee's schedule.
# """
# employee = get_object_or_404(
# Employee,
# pk=employee_id,
# tenant=request.user.tenant
# )
#
# # Get current schedule
# current_schedule = Schedule.objects.filter(
# employee=employee,
# is_current=True,
# is_active=True
# ).first()
#
# # Get upcoming assignments
# today = timezone.now().date()
# upcoming_assignments = ScheduleAssignment.objects.filter(
# schedule__employee=employee,
# assignment_date__gte=today
# ).order_by('assignment_date', 'start_time')[:14] # Next 2 weeks
#
# context = {
# 'employee': employee,
# 'current_schedule': current_schedule,
# 'upcoming_assignments': upcoming_assignments,
# }
#
# return render(request, 'hr/employee_schedule.html', context)
#
@login_required
def employee_schedule(request, employee_id):
"""
View an employee's schedule.
"""
employee = get_object_or_404(
Employee,
pk=employee_id,
tenant=request.user.tenant
)
# Get current schedule
current_schedule = Schedule.objects.filter(
employee=employee,
is_current=True,
is_active=True
).first()
# Get upcoming assignments
today = timezone.now().date()
upcoming_assignments = ScheduleAssignment.objects.filter(
schedule__employee=employee,
assignment_date__gte=today
).order_by('assignment_date', 'start_time')[:14] # Next 2 weeks
context = {
'employee': employee,
'current_schedule': current_schedule,
'upcoming_assignments': upcoming_assignments,
}
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')
}),
('Current Status', {
'fields': ('status', 'current_patient', 'current_admission',
'fields': ('status', 'current_admission',
'occupied_since', 'reserved_until')
}),
('Equipment & Features', {
@ -451,6 +451,9 @@ class SurgeryScheduleAdmin(admin.ModelAdmin):
('Basic Information', {
'fields': ('tenant', 'surgery_id', 'patient', 'admission')
}),
# ('Operating Room',{
# 'fields': ('operating_room')
# }),
('Surgery Details', {
'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(
tenant=user.tenant,
is_active=True,
role__in=['NURSE', 'NURSE_MANAGER']
employee_profile__role__in=['NURSE', 'NURSE_MANAGER']
).order_by('last_name', 'first_name')
def clean_total_beds(self):

View File

@ -1157,6 +1157,7 @@ class Transfer(models.Model):
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('DELAYED', 'Delayed'),
('REJECTED', 'Rejected'),
]
PRIORITY_CHOICES = [
('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>Status</td><td>
{% if admission.status == 'ADMITTED' %}
<span class="badge bg-success">{{ admission.get_status_display }}</span>
<span class="badge bg-success">
{% elif admission.status == 'PENDING' %}
<span class="badge bg-warning">{{ admission.get_status_display }}</span>
<span class="badge bg-warning">
{% elif admission.status == 'TRANSFERRED' %}
<span class="badge bg-info">{{ admission.get_status_display }}</span>
<span class="badge bg-info">
{% elif admission.status == 'DISCHARGED' %}
<span class="badge bg-secondary">{{ admission.get_status_display }}</span>
<span class="badge bg-secondary">
{% 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 %}
{{ admission.get_status_display }}</span>
</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>

View File

@ -4,414 +4,432 @@
{% block title %}Bed {{ object.bed_number }} Details{% endblock %}
{% 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>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'inpatients:dashboard' %}">Inpatients</a></li>
<li class="breadcrumb-item"><a href="{% url 'inpatients:bed_management' %}">Bed Management</a></li>
<li class="breadcrumb-item active">Bed {{ object.bed_number }}</li>
</ol>
<h1 class="page-header mb-0">Bed {{ object.bed_number }} Details</h1>
<h1 class="h2">
<i class="fas fa-bed me-2"></i>Bed<span class="fw-light">Details</span>
</h1>
<p class="text-muted">{{ object.bed_number }}</p>
</div>
<div class="ms-auto">
<div class="btn-group">
<a href="{% url 'inpatients:bed_update' object.pk %}" class="btn btn-primary">
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<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
</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>
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('available')">
<i class="fas fa-check text-success me-2"></i>Mark Available
</a></li>
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('occupied')">
<i class="fas fa-user text-warning me-2"></i>Mark Occupied
</a></li>
<li><a class="dropdown-item" href="#" onclick="updateBedStatus('maintenance')">
<i class="fas fa-tools text-info me-2"></i>Mark for Maintenance
</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>
<li><hr class="dropdown-divider"></li>
{# <li><a class="dropdown-item text-danger" href="{% url 'inpatients:bed_confirm_delete' object.pk %}">#}
{# <i class="fas fa-trash me-2"></i>Delete Bed#}
{# </a></li>#}
<li>
<a class="dropdown-item" href="#" onclick="updateBedStatus('available')">
<i class="fas fa-check text-success me-2"></i>Mark Available
</a>
</li>
<li>
<a class="dropdown-item" href="#" onclick="updateBedStatus('occupied')">
<i class="fas fa-user text-warning me-2"></i>Mark Occupied
</a>
</li>
<li>
<a class="dropdown-item" href="#" onclick="updateBedStatus('maintenance')">
<i class="fas fa-tools text-info me-2"></i>Mark for Maintenance
</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>
</div>
</div>
</div>
<div class="row">
<div class="col-xl-8">
<!-- Bed Information -->
<div class="card mb-4">
<div class="card-header">
<h4 class="card-title">
<i class="fas fa-bed me-2"></i>
Bed Information
</h4>
<div class="card-toolbar">
<span class="badge bg-{% if object.status == 'available' %}success{% elif object.status == 'occupied' %}warning{% elif object.status == 'maintenance' %}danger{% else %}info{% endif %} fs-6">
{{ object.get_status_display }}
</span>
</div>
</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 class="container-fluid">
<div class="row">
<div class="col-xl-9">
<!-- Bed Information -->
<div class="panel panel-inverse" data-sortable-id="index-1">
<div class="panel-heading">
<h4 class="panel-title"><i class="fa fa-bed me-2"></i>Bed Information</h4>
<div class="panel-heading-btn">
<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">
{{ object.get_status_display }}
</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>
{% if object.notes %}
<div class="mt-3">
<h6>Notes:</h6>
<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>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<table class="table table-borderless">
<tr>
<th>Date</th>
<th>Action</th>
<th>Patient</th>
<th>Duration</th>
<th>Notes</th>
<td class="fw-bold">Bed Number:</td>
<td>{{ object.bed_number }}</td>
</tr>
</thead>
<tbody>
{% for history in bed_history %}
<tr>
<td class="fw-bold">Ward:</td>
<td>
<div>{{ history.date|date:"M d, Y" }}</div>
<div class="small text-muted">{{ history.date|time:"g:i A" }}</div>
<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 history.action == 'admission' %}success{% elif history.action == 'discharge' %}info{% elif history.action == 'transfer' %}warning{% else %}secondary{% endif %}">
{{ history.get_action_display }}
<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 history.patient %}
<a href="{% url 'patients:patient_detail' history.patient.pk %}">
{{ 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>
{% if object.has_oxygen %}
<span class="badge bg-info me-1">Oxygen</span>
{% endif %}
</td>
<td>
{% if history.duration %}
{{ history.duration }}
{% else %}
<span class="text-muted">-</span>
{% if object.has_suction %}
<span class="badge bg-info me-1">Suction</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>
{% 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>
{% endfor %}
</tbody>
</table>
<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>
{% 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">
<!-- Quick Actions -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title">
<i class="fas fa-bolt me-2"></i>
Quick Actions
</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>
{% if object.notes %}
<div class="mt-3">
<h6>Notes:</h6>
<div class="note alert-info">
{{ object.notes }}
</div>
</div>
{% 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>
<!-- Bed Statistics -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title">
<i class="fas fa-chart-bar me-2"></i>
Statistics
</h5>
</div>
<div class="card-body">
<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>
<!-- Current Patient -->
{% if object.current_admission.patient %}
<div class="panel panel-inverse" data-sortable-id="index-2">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-user-injured me-2"></i>Current Patient
</h4>
<div class="panel-heading-btn">
<a href="{% url 'inpatients:admission_detail' object.current_admission.pk %}" class="btn btn-xs btn-outline-theme">
<i class="fas fa-eye me-2"></i>View Admission
</a>
<a href="javascript:" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
<a href="javascript:" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
<a href="javascript:" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
<a href="javascript:" class="btn btn-xs btn-icon btn-danger" data-toggle="panel-remove"><i class="fa fa-times"></i></a>
</div>
</div>
<div class="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>
<!-- 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="panel-body">
<div class="d-flex align-items-center">
<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>
<h5 class="mb-1">
<a href="{% url 'patients:patient_detail' object.current_admission.patient.pk %}">
{{ object.current_admission.patient.get_full_name }}
</a>
</h5>
<div class="text-muted mb-1">
<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>
{% 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>
</div>
{% endif %}
<!-- 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>
{% 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>
<!-- Ward Information -->
<div class="card">
<div class="card-header">
<h5 class="card-title">
<i class="fas fa-hospital me-2"></i>
Ward Information
</h5>
<div class="col-xl-3">
<!-- Quick Actions -->
<div class="panel panel-inverse" data-sortable-id="index-4">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-bolt me-2"></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 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 class="card-body">
<div class="mb-2">
<strong>Ward:</strong>
<a href="{% url 'inpatients:ward_detail' object.ward.pk %}">
{{ object.ward.name }}
</a>
<!-- Bed Statistics -->
<div class="panel panel-inverse" data-sortable-id="index-5">
<div class="panel-heading">
<h4 class="panel-title">
<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 class="mb-2">
<strong>Department:</strong> {{ object.ward.department.name }}
<div class="panel-body">
<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 class="mb-2">
<strong>Capacity:</strong> {{ object.ward.total_beds }} beds
</div>
<!-- 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 class="mb-2">
<strong>Available:</strong> {{ object.ward.available_beds }} beds
<div class="panel-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="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>
<strong>Occupancy:</strong> {{ object.ward.occupancy_rate }}%
</div>
<!-- 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>
<!-- Update Status Modal -->
<div class="modal fade" id="updateStatusModal" tabindex="-1">
<div class="modal-dialog">

View File

@ -4,21 +4,22 @@
{% block title %}Bed Management{% endblock %}
{% 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>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'inpatients:dashboard' %}">Inpatients</a></li>
<li class="breadcrumb-item active">Bed Management</li>
</ol>
<h1 class="page-header mb-0">Bed Management</h1>
<h1 class="h2">
<i class="fas fa-list me-2"></i>Bed<span class="fw-light">Management</span>
</h1>
<p class="text-muted">Real-time bed occupancy and availability management.</p>
</div>
<div class="ms-auto">
<div class="btn-toolbar mb-2 mb-md-0">
<div class="ms-auto">
<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
</a>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle Dropdown</span>
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportBeds()">
@ -34,94 +35,27 @@
</ul>
</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 class="container-fluid">
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">
<i class="fas fa-bed me-2"></i>
Bed List
</h4>
<div class="card-toolbar">
<div class="d-flex align-items-center">
<!-- View Toggle -->
<div class="btn-group btn-group-sm me-3" role="group">
<input type="radio" class="btn-check" name="viewType" id="gridView" autocomplete="off" checked>
<label class="btn btn-outline-secondary" for="gridView">
<i class="fas fa-th"></i>
</label>
<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>
<div class="panel panel-inverse" data-sortable-id="index-1">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fa fa-bed me-2"></i>Bed List
</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-flex align-items-center mb-3">
<!-- Filters -->
<div class="d-flex align-items-center">
<select class="form-select form-select-sm me-2" id="wardFilter" onchange="filterBeds()">
@ -132,127 +66,24 @@
</select>
<select class="form-select form-select-sm me-2" id="statusFilter" onchange="filterBeds()">
<option value="">All Status</option>
<option value="available">Available</option>
<option value="occupied">Occupied</option>
<option value="maintenance">Maintenance</option>
<option value="cleaning">Cleaning</option>
<option value="AVAILABLE">Available</option>
<option value="OCCUPIED">Occupied</option>
<option value="MAINTENANCE">Maintenance</option>
<option value="CLEANING">Cleaning</option>
</select>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearFilters()">
<i class="fas fa-times"></i>
</button>
</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 -->
<div id="listViewContent" style="display: none;">
<div id="listViewContent">
{% if object_list %}
<div class="table-responsive">
<div class="table-responsive border border-primary-subtle">
<table class="table table-hover" id="bedTable">
<thead>
<thead class="table-primary">
<tr>
<th>
<input type="checkbox" id="selectAll" onchange="toggleSelectAll()">
@ -269,8 +100,8 @@
</thead>
<tbody>
{% for bed in object_list %}
<tr class="bed-row"
data-ward="{{ bed.ward.id }}"
<tr class="bed-row"
data-ward="{{ bed.ward.id }}"
data-status="{{ bed.status }}"
data-bed-type="{{ bed.bed_type }}">
<td>
@ -290,11 +121,11 @@
</span>
</td>
<td>
{% if bed.current_patient %}
<a href="{% url 'patients:patient_detail' bed.current_patient.pk %}">
{{ bed.current_patient.get_full_name }}
{% if bed.current_admission.patient %}
<a href="{% url 'patients:patient_detail' bed.current_admission.patient.pk %}">
{{ bed.current_admission.patient.get_full_name }}
</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 %}
<span class="text-muted">-</span>
{% endif %}
@ -318,7 +149,7 @@
<a href="{% url 'inpatients:bed_detail' bed.pk %}" class="btn btn-outline-primary">
<i class="fas fa-eye"></i>
</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>
</a>
<button type="button" class="btn btn-outline-danger" onclick="deleteBed('{{ bed.pk }}')">
@ -342,29 +173,9 @@
</div>
</div>
</div>
</div>
<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() {
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="col-12">
<div class="planning-header">
<div class="row">
<div class="row mb-2">
<div class="col-md-8">
<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>
@ -189,33 +189,46 @@
<p class="mt-2 mb-0">Expected Discharge: {{ admission.anticipated_discharge_date}}</p>
</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 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 -->
<div class="row">
<div class="col-md-8">
<!-- Discharge Timeline -->
<div class="panel panel-inverse border border-primary">
<div class="panel-heading bg-primary">
<h4 class="panel-title"><i class="fas fa-timeline"></i> Discharge Timeline</h4>
<div class="panel panel-inverse">
<div class="panel-heading">
<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 class="panel-body">
<div class="timeline">
{% %}
<div class="timeline-item">
<div class="timeline-time">
<span class="date">today</span>
@ -242,11 +255,19 @@
</div>
<!-- Discharge Checklist -->
<div class="card mt-3">
<div class="card-header">
<h5><i class="fas fa-tasks"></i> Discharge Checklist</h5>
<div class="panel panel-inverse">
<div class="panel-heading">
<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 class="card-body">
<div class="panel-body">
<div class="mb-2">
<h6>Medical Clearance</h6>
<div class="checklist-item">
@ -332,11 +353,19 @@
<div class="col-md-4">
<!-- Risk Assessment -->
<div class="card">
<div class="card-header">
<h5><i class="fas fa-exclamation-triangle"></i> Risk Assessment</h5>
</div>
<div class="card-body">
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-exclamation-triangle"></i> Risk Assessment
</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">
<strong>Readmission Risk:</strong> Moderate
<br><small>Age >65, cardiac condition, multiple medications</small>
@ -357,11 +386,19 @@
</div>
<!-- Care Team -->
<div class="card mt-3">
<div class="card-header">
<h5><i class="fas fa-users"></i> Discharge Care Team</h5>
<div class="panel panel-inverse">
<div class="panel-heading">
<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 class="card-body">
<div class="panel-body">
<div class="care-team">
<div class="team-member">
<i class="fas fa-user-md fa-2x text-primary mb-2"></i>
@ -388,11 +425,19 @@
</div>
<!-- Quick Actions -->
<div class="card mt-3">
<div class="card-header">
<h5><i class="fas fa-bolt"></i> Quick Actions</h5>
<div class="panel panel-inverse">
<div class="panel-heading">
<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 class="card-body">
<div class="panel-body">
<div class="d-grid gap-2">
<button class="btn btn-outline-primary btn-sm">
<i class="fas fa-calendar"></i> Schedule Follow-up
@ -415,11 +460,19 @@
<!-- Discharge Medications -->
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-pills"></i> Discharge Medications</h5>
<div class="panel panel-inverse">
<div class="panel-heading">
<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 class="card-body">
<div class="panel-body">
<div class="medication-item">
<h6>Aspirin 81mg</h6>
<p>Take 1 tablet daily by mouth</p>
@ -445,11 +498,19 @@
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-calendar-check"></i> Follow-up Appointments</h5>
<div class="panel panel-inverse">
<div class="panel-heading">
<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 class="card-body">
<div class="panel-body">
<div class="appointment-item">
<h6>Cardiology Follow-up</h6>
<p><strong>Date:</strong> January 30, 2024 at 2:00 PM</p>
@ -481,25 +542,13 @@
<div class="col-12">
<div class="card">
<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>
{% 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>
</div>
<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 %}
<div class="bed-info mb-3">
@ -26,9 +26,9 @@
</div>
</div>
{% if bed.current_patient %}
{% if bed.current_admission.patient %}
<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.
</div>
{% endif %}

View File

@ -4,17 +4,35 @@
{% block title %}Surgery Schedule - {{ block.super }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div>
<h1 class="h2">
<i class="fas fa-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="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">
<i class="fas fa-procedures me-2"></i>Surgery Schedule
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-list-dots"></i> List
</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 class="panel-body">
<!-- Filters -->
<div class="row mb-3">
<div class="col-md-2">
@ -52,12 +70,13 @@
</div>
<!-- Surgery Schedule Table -->
<div class="table-responsive">
<div class="table-responsive border border-primary-subtle">
<table class="table table-hover">
<thead>
<thead class="table-primary">
<tr>
<th>Date/Time</th>
<th>Patient</th>
<th>Room</th>
<th>Procedure</th>
<th>Surgeon</th>
<th>Type</th>
@ -81,6 +100,12 @@
</small>
</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>
{% if surgery.procedure_code %}
<br><small class="text-muted">Code: {{ surgery.procedure_code }}</small>
@ -125,9 +150,9 @@
<i class="fas fa-check"></i>
</button>
{% elif surgery.status == 'CONFIRMED' %}
<button class="btn btn-outline-primary"
<button class="btn btn-outline-primary"
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-swap="none"
hx-headers='{"X-CSRFToken":"{{ csrf_token }}"}'>
@ -153,18 +178,18 @@
</button>
{% 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>
</button>
</a>
{% if surgery.status not in 'COMPLETED,CANCELLED' %}
{# <button class="btn btn-outline-warning" #}
{# title="Postpone"#}
{# hx-post="{% url 'inpatients:s' surgery.id %}"#}
{# hx-confirm="Postpone this surgery?"#}
{# hx-swap="none">#}
{# <i class="fas fa-clock"></i>#}
{# </button>#}
<button class="btn btn-outline-warning"
title="Postpone"
hx-post="{% url 'inpatients:postpone_surgery' surgery.id %}"
hx-confirm="Postpone this surgery?"
hx-swap="none">
<i class="fas fa-clock"></i>
</button>
<button class="btn btn-outline-danger"
title="Cancel"
hx-post="{% url 'inpatients:cancel_surgery' surgery.id %}"
@ -188,38 +213,11 @@
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Surgery pagination">
<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>
{% include 'partial/pagination.html' %}
{% endif %}
</div>
</div>
</div>
</div>

View File

@ -1,11 +1,8 @@
{% 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>
</div>
<div class="modal-body">
<form id="transferForm" method="post" action="{% url 'inpatients:request_transfer' admission_id=admission.id %}">
<form id="transferForm" method="post" action="{% url 'inpatients:transfer_patient' admission.id %}">
{% csrf_token %}
<div class="patient-info mb-3">
@ -103,7 +100,7 @@
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" form="transferForm" class="btn btn-primary">

View File

@ -4,17 +4,35 @@
{% block title %}Transfer Management - {{ block.super }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div>
<h1 class="h2">
<i class="fas fa-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="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">
<i class="fas fa-exchange-alt me-2"></i>Transfer Management
<div class="panel panel-inverse" data-sortable-id="index-1">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-exchange me-2"></i>Transfer 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="card-body">
<div class="panel-body">
<!-- Filters -->
<div class="row mb-3">
<div class="col-md-2">
@ -51,9 +69,9 @@
</div>
<!-- Transfers Table -->
<div class="table-responsive">
<div class="table-responsive border border-primary-subtle">
<table class="table table-hover">
<thead>
<thead class="table-primary">
<tr>
<th>Patient</th>
<th>Requested</th>
@ -121,18 +139,23 @@
<td>
<div class="btn-group btn-group-sm">
{% if transfer.status == 'REQUESTED' %}
<a class="btn btn-outline-success"
<button class="btn btn-outline-success"
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>
</a>
{# <button class="btn btn-outline-danger" #}
{# title="Reject"#}
{# hx-post="{% url 'inpatients:reject_transfer' transfer.id %}"#}
{# hx-confirm="Reject this transfer?"#}
{# hx-swap="none">#}
{# <i class="fas fa-times"></i>#}
{# </button>#}
</button>
<button class="btn btn-outline-danger"
title="Reject"
hx-post="{% url 'inpatients:reject_transfer' transfer.id %}"
hx-confirm="Reject this transfer?"
hx-swap="none"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<i class="fas fa-times"></i>
</button>
{% elif transfer.status == 'APPROVED' or transfer.status == 'SCHEDULED' %}
{# <button class="btn btn-outline-primary" #}
{# title="Execute Transfer"#}
@ -143,18 +166,19 @@
{# </button>#}
{% 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>
</button>
</a>
{% if transfer.status not in 'COMPLETED,CANCELLED,REJECTED' %}
{# <button class="btn btn-outline-secondary" #}
{# title="Cancel"#}
{# hx-post="{% url 'inpatients:cancel_transfer' transfer.id %}"#}
{# hx-confirm="Cancel this transfer?"#}
{# hx-swap="none">#}
{# <i class="fas fa-ban"></i>#}
{# </button>#}
<button class="btn btn-outline-warning"
title="Cancel"
hx-post="{% url 'inpatients:cancel_transfer' transfer.id %}"
hx-confirm="Cancel this transfer?"
hx-swap="none"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<i class="fas fa-ban"></i>
</button>
{% endif %}
</div>
</td>
@ -170,12 +194,11 @@
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
<!-- Pagination -->
{% if is_paginated %}
{% include 'partial/pagination.html'%}
{% endif %}
</div>
</div>
</div>
</div>

View File

@ -4,20 +4,14 @@
{% block title %}{{ ward.name }} - Ward Details - {{ block.super }}{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- 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>
<h1 class="h3 mb-1">
<i class="fas fa-hospital me-2"></i>{{ ward.name }}
</h1>
<nav aria-label="breadcrumb">
<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>
<h1 class="h2">
<i class="fas fa-hospital me-2"></i>{{ ward.name }}<span class="fw-light">Details</span>
</h1>
<p class="text-muted">Manage ward aspects</p>
</div>
<div class="btn-group">
<a href="{% url 'inpatients:ward_list' %}" class="btn btn-outline-secondary">
@ -48,18 +42,25 @@
</div>
</div>
</div>
<div class="container-fluid">
<div class="row">
<!-- Main Content -->
<div class="col-lg-8">
<!-- Ward Information -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<div class="panel panel-inverse" data-sortable-id="index-1">
<div class="panel-heading">
<h4 class="panel-title">
<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 class="card-body">
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<dl class="row">
@ -132,35 +133,37 @@
</div>
<!-- Bed Layout -->
<div class="card mb-4">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-bed me-2"></i>Bed Layout
</h5>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary" onclick="refreshBedLayout()">
<i class="fas fa-sync-alt"></i>
</button>
<button type="button" class="btn btn-outline-primary" onclick="toggleBedView()">
<i class="fas fa-th me-1"></i>Grid View
</button>
</div>
<div class="panel panel-inverse" data-sortable-id="index-2">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-bed me-2"></i>Bed Layout
</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 class="panel-body">
<div id="bed-layout" class="bed-grid">
{% 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-number">{{ bed.bed_number }}</div>
<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 }}
</span>
</div>
{% if bed.current_patient %}
{% if bed.current_admission.patient %}
<div class="patient-info">
<small>{{ bed.current_patient.get_full_name }}</small>
<small>{{ bed.current_admission.patient.get_full_name }}</small>
</div>
{% endif %}
</div>
@ -179,16 +182,20 @@
</div>
<!-- Current Patients -->
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-users me-2"></i>Current Patients
</h5>
<span class="badge bg-primary">{{ current_patients.count }} patients</span>
<div class="panel panel-inverse" data-sortable-id="index-3">
<div class="panel-heading">
<h4 class="panel-title">
<i class="fas fa-users me-2"></i>Current Patients
</h4>
<div class="panel-heading-btn">
<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 class="card-body p-0">
<div class="panel-body">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
@ -202,10 +209,12 @@
</tr>
</thead>
<tbody>
{% for admission in current_patients %}
{% for admission in recent_admissions %}
<tr>
<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>
<div class="d-flex align-items-center">
@ -216,17 +225,17 @@
</div>
<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>
</td>
<td>
<div>{{ admission.admission_date|date:"M d, Y" }}</div>
<small class="text-muted">{{ admission.admission_date|timesince }} ago</small>
<div>{{ admission.admission_datetime|date:"M d, Y" }}</div>
<small class="text-muted">{{ admission.admission_datetime|timesince }} ago</small>
</td>
<td>
{% if admission.primary_diagnosis %}
{{ admission.primary_diagnosis|truncatechars:30 }}
{% if admission.admitting_diagnosis %}
{{ admission.admitting_diagnosis|truncatechars:30 }}
{% else %}
<span class="text-muted">Not specified</span>
{% endif %}
@ -244,21 +253,15 @@
class="btn btn-outline-primary btn-sm" title="View Details">
<i class="fas fa-eye"></i>
</a>
<button type="button" class="btn btn-outline-secondary btn-sm 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="#">
<i class="fas fa-exchange-alt me-2"></i>Transfer
</a></li>
<li><a class="dropdown-item" href="#">
<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>
<a class="btn btn-outline-warning btn-sm" onclick="showTransfer({{ admission.id }})">
<i class="fas fa-exchange-alt"></i>
</a>
<a class="btn btn-outline-danger btn-sm" href="{% url 'inpatients:discharge_patient' admission.id %}">
<i class="fas fa-sign-out-alt"></i>
</a>
<a class="btn btn-outline-info btn-sm" href="#">
<i class="fas fa-notes-medical"></i>
</a>
</div>
</td>
</tr>
@ -281,18 +284,24 @@
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Ward Statistics -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<div class="panel panel-inverse" data-sortable-id="index-4">
<div class="panel-heading">
<h4 class="panel-title">
<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 class="card-body">
<div class="panel-body">
<div class="row text-center">
<div class="col-6 mb-3">
<div class="card bg-success bg-gradient text-white">
<div class="card-body">
<h4 class="mb-1">{{ ward.available_beds }}</h4>
<h4 class="mb-1">{{ available_beds }}</h4>
<small>Available</small>
</div>
</div>
@ -300,7 +309,7 @@
<div class="col-6 mb-3">
<div class="card bg-primary bg-gradient text-white">
<div class="card-body">
<h4 class="mb-1">{{ ward.occupied_beds }}</h4>
<h4 class="mb-1">{{ occupied_beds }}</h4>
<small>Occupied</small>
</div>
</div>
@ -308,15 +317,15 @@
<div class="col-6 mb-3">
<div class="card bg-warning bg-gradient text-white">
<div class="card-body">
<h4 class="mb-1">{{ ward.maintenance_beds }}</h4>
<h4 class="mb-1">{{ maintenance_beds }}</h4>
<small>Maintenance</small>
</div>
</div>
</div>
<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">
<h4 class="mb-1">{{ ward.reserved_beds }}</h4>
<h4 class="mb-1">{{ reserved_beds }}</h4>
<small>Reserved</small>
</div>
</div>
@ -328,7 +337,7 @@
<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 %}"
style="width: {{ ward.occupancy_rate }}%">
{{ ward.occupancy_rate|floatformat:1 }}%
{{ ward.occupancy_rate|floatformat:0 }}%
</div>
</div>
<small class="text-muted">{{ ward.occupied_beds }} of {{ ward.total_beds }} beds occupied</small>
@ -337,13 +346,19 @@
</div>
<!-- Quick Actions -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<div class="panel panel-inverse" data-sortable-id="index-5">
<div class="panel-heading">
<h4 class="panel-title">
<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 class="panel-body">
<div class="d-grid gap-2">
<a href="#" class="btn btn-primary">
<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">
<i class="fas fa-bed me-2"></i>Manage Beds
</a>
<a href="#" class="btn btn-outline-info">
<i class="fas fa-exchange-alt me-2"></i>Transfer Patient
</a>
<a href="#" class="btn btn-outline-success">
<i class="fas fa-sign-out-alt me-2"></i>Discharge Patient
<a href="{% url 'inpatients:transfer_management' %}" class="btn btn-outline-info">
<i class="fas fa-exchange-alt me-2"></i>Transfer Management
</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">
<i class="fas fa-chart-bar me-2"></i>Ward Report
</a>
@ -368,13 +383,19 @@
</div>
<!-- Ward Staff -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<div class="panel panel-inverse" data-sortable-id="index-6">
<div class="panel-heading">
<h4 class="panel-title">
<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 class="card-body">
<div class="panel-body">
<div class="list-group list-group-flush">
{% if ward.nurse_manager %}
<div class="list-group-item d-flex justify-content-between align-items-center px-0">
@ -422,6 +443,19 @@
</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>
// Ward detail functionality
function refreshBedLayout() {
@ -436,7 +470,7 @@ function toggleBedView() {
function showBedDetails(bedId) {
// Load bed details in modal
fetch(`/inpatients/beds/${bedId}/details/`)
fetch(`{% url 'inpatients:bed_details' 0%}`.replace('0', bedId))
.then(response => response.text())
.then(html => {
document.getElementById('bedDetailsContent').innerHTML = html;
@ -446,6 +480,18 @@ function showBedDetails(bedId) {
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() {
window.print();

View File

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

View File

@ -13,6 +13,8 @@ urlpatterns = [
# wards
path('wards/', views.WardListView.as_view(), name='ward_list'),
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'),
# Admissions
@ -30,11 +32,12 @@ urlpatterns = [
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>/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.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/<int:pk>/', views.BedDetailView.as_view(), name='bed_detail'),
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>/maintenance/', views.maintenance_bed, name='maintenance_bed'),
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/calendar/', views.surgery_calendar, name='surgery_calendar'),
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

View File

@ -1,6 +1,7 @@
"""
Views for inpatients app.
"""
from django.conf.locale import te
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required, permission_required
@ -172,13 +173,13 @@ class WardListView(LoginRequiredMixin, ListView):
context['buildings'] = Ward.objects.filter(
tenant=tenant
).exclude(building__isnull=True).exclude(building='').values_list(
).values_list(
'building', flat=True
).distinct()
context['floors'] = Ward.objects.filter(
tenant=tenant
).exclude(floor__isnull=True).exclude(floor='').values_list(
).values_list(
'floor', flat=True
).distinct()
@ -215,7 +216,7 @@ class WardDetailView(LoginRequiredMixin, DetailView):
context['beds'] = Bed.objects.filter(
ward=ward
).select_related(
'current_patient', 'current_admission'
'current_admission'
).order_by('room_number', 'bed_number')
# Group beds by room for display
@ -234,6 +235,7 @@ class WardDetailView(LoginRequiredMixin, DetailView):
context['maintenance_beds'] = context['beds'].filter(
status__in=['MAINTENANCE', 'OUT_OF_ORDER', 'CLEANING']
).count()
context['reserved_beds'] = context['beds'].filter(status='RESERVED').count()
if context['total_beds'] > 0:
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."""
queryset = Bed.objects.filter(
ward__tenant=self.request.user.tenant
).select_related('ward', 'current_patient')
).select_related('ward', 'current_admission')
# Handle search query
search_query = self.request.GET.get('search', '')
@ -394,13 +396,13 @@ class BedListView(LoginRequiredMixin, ListView):
).order_by('name')
# 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
context['room_types'] = Bed.ROOM_TYPE_CHOICES
context['room_types'] = Bed.RoomType.choices
# Get statuses for filter dropdown
context['statuses'] = Bed.STATUS_CHOICES
context['statuses'] = Bed.BedStatus.choices
# Add search query to context
context['search_query'] = self.request.GET.get('search', '')
@ -426,7 +428,7 @@ class BedDetailView(LoginRequiredMixin, DetailView):
return Bed.objects.filter(
ward__tenant=self.request.user.tenant
).select_related(
'ward', 'current_patient', 'current_admission',
'ward', 'current_admission',
'cleaned_by', 'blocked_by', 'created_by'
)
@ -436,13 +438,12 @@ class BedDetailView(LoginRequiredMixin, DetailView):
# Get bed history - admissions that used this bed
context['admission_history'] = Admission.objects.filter(
Q(current_bed=bed) | Q(current_bed=bed)
).select_related(
'patient', 'admitting_physician'
).order_by('-admission_datetime')[:10]
current_bed=bed).select_related(
'patient', 'admitting_physician',
).order_by('-admission_datetime')
# 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
@ -562,7 +563,7 @@ class BedManagementView(LoginRequiredMixin, ListView):
)
return queryset.select_related(
'ward', 'current_patient', 'current_admission'
'ward', 'current_admission'
).order_by('ward__name', 'room_number', 'bed_number')
def get_context_data(self, **kwargs):
@ -583,8 +584,8 @@ class BedManagementView(LoginRequiredMixin, ListView):
)
context.update({
'wards': Ward.objects.filter(tenant=tenant, is_active=True),
'bed_statuses': Bed.STATUS_CHOICES,
'bed_types': Bed.BED_TYPE_CHOICES,
'bed_statuses': Bed.BedStatus.choices,
'bed_types': Bed.BedType.choices,
'total_beds': Bed.objects.filter(ward__tenant=tenant).count(),
'available_beds': Bed.objects.filter(ward__tenant=tenant, status='AVAILABLE').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()
active_admissions = Admission.objects.filter(tenant=tenant, status='ADMITTED').count()
context.update({
'admission_statuses': Admission.STATUS_CHOICES,
'admission_types': Admission.ADMISSION_TYPE_CHOICES,
'admission_statuses': Admission.AdmissionStatus.choices,
'admission_types': Admission.AdmissionType.choices,
'wards': Ward.objects.filter(tenant=tenant, is_active=True),
'total_admissions': total_admissions,
'active_admissions': active_admissions,
@ -880,7 +881,7 @@ class SurgeryScheduleListView(LoginRequiredMixin, ListView):
model = SurgerySchedule
template_name = 'inpatients/surgeries/surgery_schedule.html'
context_object_name = 'surgeries'
paginate_by = 20
paginate_by = 5
def get_queryset(self):
"""Filter surgeries by tenant and search query."""
@ -959,10 +960,10 @@ class SurgeryScheduleListView(LoginRequiredMixin, ListView):
is_active=True,
employee_profile__role__in=['SURGEON', 'PHYSICIAN_ASSISTANT']
).order_by('last_name', 'first_name')
from operating_theatre.models import OperatingRoom
# Get operating rooms for filter dropdown
try:
from operating_theatre.models import OperatingRoom
context['operating_rooms'] = OperatingRoom.objects.filter(
tenant=tenant,
is_active=True
@ -1102,11 +1103,11 @@ def bed_grid(request):
beds = Bed.objects.filter(
ward_id=ward_id,
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:
beds = Bed.objects.filter(
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'
)
@ -1271,7 +1272,7 @@ def transfer_patient(request, admission_id):
'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
@ -1279,7 +1280,6 @@ def approve_transfer(request, transfer_id):
"""
HTMX endpoint for approving a transfer.
"""
print("transfer clicked")
transfer = get_object_or_404(Transfer, id=transfer_id, admission__tenant=request.user.tenant)
if request.method == 'POST':
@ -1303,7 +1303,7 @@ def approve_transfer(request, transfer_id):
# Log the action
AuditLogger.log_event(
actor=request.user,
user=request.user,
action='TRANSFER_APPROVED',
target=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)
@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
def complete_transfer(request, transfer_id):
"""
@ -1517,11 +1583,19 @@ def bed_status_board(request):
'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
})
@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
def clean_bed(request, pk):
"""
@ -2261,8 +2335,7 @@ class AdmissionDetailView(LoginRequiredMixin, DetailView):
context['has_discharge_summary'] = hasattr(admission, 'discharge_summary')
return context
#
#
@ -2541,38 +2614,7 @@ class TransferUpdateView(LoginRequiredMixin, UpdateView):
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
@ -2676,6 +2718,109 @@ def cancel_surgery(request, pk):
'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
def inpatient_stats(request):
"""
@ -2775,3 +2920,6 @@ def inpatient_stats(request):
'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
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)
self.procedures = [
"استئصال المرارة بالمنظار", "Laparoscopic Cholecystectomy",
"جراحة القلب المفتوح", "Open Heart Surgery",
"استبدال مفصل الركبة", "Total Knee Replacement",
"جراحة الفتق الإربي", "Inguinal Hernia Repair",
"استئصال الزائدة الدودية", "Appendectomy",
"جراحة المنظار التشخيصي", "Diagnostic Laparoscopy",
"استئصال الغدة الدرقية", "Thyroidectomy",
"جراحة الساد", "Cataract Surgery",
"استئصال اللوزتين", "Tonsillectomy",
"جراحة البواسير", "Hemorrhoidectomy"
"Laparoscopic Cholecystectomy","Open Heart Surgery",
"Total Knee Replacement", "Inguinal Hernia Repair",
"Appendectomy", "Diagnostic Laparoscopy",
"Thyroidectomy", "Cataract Surgery",
"Tonsillectomy", "Hemorrhoidectomy"
]
# Saudi medical equipment (common in Saudi hospitals)
self.equipment = [
"منظار البطن كارل زايس", "Carl Zeiss Laparoscope",
"جهاز القطع الكهربائي", "Electrocautery Unit",
"جهاز التخدير دريجر", "Drager Anesthesia Machine",
"مجهر جراحي ليكا", "Leica Surgical Microscope",
"روبوت دافنشي", "da Vinci Surgical Robot",
"جهاز الأشعة المقطعية المحمول", "Portable CT Scanner",
"منظار المثانة", "Cystoscope",
"مضخة القلب الرئة", "Heart-Lung Machine"
"Carl Zeiss Laparoscope","Electrocautery Unit",
"Drager Anesthesia Machine", "Leica Surgical Microscope",
"da Vinci Surgical Robot", "Portable CT Scanner",
"Cystoscope", "Heart-Lung Machine"
]
# Common diagnoses in Arabic and English
self.diagnoses = [
"التهاب المرارة الحاد", "Acute Cholecystitis",
"انسداد الأمعاء", "Bowel Obstruction",
"كسر في عظم الفخذ", "Femur Fracture",
"أورام الغدة الدرقية", "Thyroid Nodules",
"حصى الكلى", "Kidney Stones",
"التهاب الزائدة الدودية", "Appendicitis",
"الفتق الإربي", "Inguinal Hernia",
"سرطان القولون", "Colorectal Cancer"
"Acute Cholecystitis", "Bowel Obstruction",
"Femur Fracture", "Thyroid Nodules",
"Kidney Stones", "Appendicitis",
"Inguinal Hernia", "Colorectal Cancer"
]
def get_existing_data(self):
@ -795,15 +782,15 @@ class OperatingTheatreDataGenerator:
# Try to categorize users by role/specialty
surgeons = list(User.objects.filter(
groups__name__icontains='surgeon'
groups__name__icontains='SURGEON'
).distinct()) or users[:max(1, len(users) // 3)]
anesthesiologists = list(User.objects.filter(
groups__name__icontains='anesthesia'
groups__name__icontains='ANESTHESIA'
).distinct()) or users[len(users) // 3:2 * len(users) // 3]
nurses = list(User.objects.filter(
groups__name__icontains='nurse'
groups__name__icontains='NURSE'
).distinct()) or users[2 * len(users) // 3:]
# Get patients

View File

@ -224,7 +224,7 @@ class InsuranceInfoAdmin(admin.ModelAdmin):
('Subscriber Information', {
'fields': (
'subscriber_name', 'subscriber_relationship',
'subscriber_dob', 'subscriber_ssn'
'subscriber_dob',
)
}),
('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.