from django.contrib.auth.decorators import login_required from django.shortcuts import render, get_object_or_404, redirect from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.contrib import messages from django.urls import reverse_lazy, reverse from django.http import JsonResponse, HttpResponse from django.utils import timezone from django.db.models import Sum, Count, Q, Avg, F from django.db import transaction from decimal import Decimal import csv from datetime import datetime, timedelta from .models import * from .forms import * class FacilityDashboardView(LoginRequiredMixin, TemplateView): template_name = 'facility_management/dashboard.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['page_title'] = 'Facility Management Dashboard' # Get today's date today = timezone.now().date() # Calculate statistics context['stats'] = { 'total_buildings': Building.objects.filter(is_active=True).count(), 'total_assets': Asset.objects.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=[ MaintenanceRequest.MaintenanceStatus.SUBMITTED, MaintenanceRequest.MaintenanceStatus.ASSIGNED, MaintenanceRequest.MaintenanceStatus.IN_PROGRESS ] ).count(), 'completed_today': MaintenanceRequest.objects.filter( completed_date__date=today, status=MaintenanceRequest.MaintenanceStatus.COMPLETED ).count(), 'total_rooms': Room.objects.count(), 'occupied_rooms': Room.objects.filter(occupancy_status=Room.OccupancyStatus.OCCUPIED).count(), 'active_contracts': ServiceContract.objects.filter(status=ServiceContract.ContractStatus.ACTIVE).count(), 'contracts_expiring_soon': ServiceContract.objects.filter( end_date__lte=today + timedelta(days=30), status=ServiceContract.ContractStatus.ACTIVE ).count(), } # Recent maintenance requests context['recent_requests'] = MaintenanceRequest.objects.select_related( 'building', 'requested_by', 'assigned_to' ).order_by('-requested_date')[:10] # Assets needing maintenance context['maintenance_alerts'] = Asset.objects.filter( next_maintenance_date__lte=today + timedelta(days=7) ).select_related('building', 'category')[:5] # Work orders by status context['work_order_stats'] = MaintenanceRequest.objects.values('status').annotate( count=Count('id') ).order_by('status') # Assets by condition context['asset_condition_stats'] = Asset.objects.values('condition').annotate( count=Count('id') ).order_by('condition') # Monthly maintenance costs current_month = today.replace(day=1) context['monthly_costs'] = MaintenanceRequest.objects.filter( completed_date__gte=current_month, status=MaintenanceRequest.MaintenanceStatus.COMPLETED ).aggregate( total_cost=Sum('actual_cost'), avg_cost=Avg('actual_cost'), count=Count('id') ) # Expiring contracts context['expiring_contracts'] = ServiceContract.objects.filter( end_date__lte=today + timedelta(days=30), status=ServiceContract.ContractStatus.ACTIVE ).select_related('vendor')[:5] return context class BuildingListView(LoginRequiredMixin, ListView): model = Building template_name = 'facility_management/buildings/list.html' context_object_name = 'buildings' paginate_by = 20 def get_queryset(self): tenant = self.request.user.tenant queryset = Building.objects.filter(tenant=tenant).order_by( 'name') # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(name__icontains=search) | Q(code__icontains=search) | Q(airport_code__icontains=search) ) # Filter by building type building_type = self.request.GET.get('building_type') if building_type: queryset = queryset.filter(building_type=building_type) # Filter by 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() return context class BuildingDetailView(LoginRequiredMixin, DetailView): model = Building template_name = 'facility_management/buildings/detail.html' context_object_name = 'building' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) building = self.get_object() # Building statistics context['building_stats'] = { 'total_floors': building.floors.count(), 'total_rooms': Room.objects.filter(floor__building=building).count(), 'occupied_rooms': Room.objects.filter(floor__building=building, occupancy_status='occupied').count(), 'total_assets': Asset.objects.filter(building=building).count(), 'operational_assets': Asset.objects.filter(building=building, status='operational').count(), } # Recent maintenance requests for this building context['recent_maintenance'] = MaintenanceRequest.objects.filter( building=building ).select_related('requested_by', 'assigned_to').order_by('-requested_date')[:5] # Assets in this building context['building_assets'] = Asset.objects.filter( building=building ).select_related('category')[:10] return context class BuildingCreateView(LoginRequiredMixin, CreateView): model = Building form_class = BuildingForm template_name = 'facility_management/buildings/form.html' success_url = reverse_lazy('facility_management:building_list') def form_valid(self, form): messages.success(self.request, 'Building created successfully.') return super().form_valid(form) class BuildingUpdateView(LoginRequiredMixin, UpdateView): model = Building form_class = BuildingForm template_name = 'facility_management/buildings/form.html' success_url = reverse_lazy('facility_management:building_list') def form_valid(self, form): messages.success(self.request, 'Building updated successfully.') return super().form_valid(form) class AssetListView(LoginRequiredMixin, ListView): model = Asset template_name = 'facility_management/assets/list.html' context_object_name = 'assets' paginate_by = 25 def get_queryset(self): queryset = Asset.objects.select_related('category', 'building').order_by('asset_id') # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(name__icontains=search) | Q(asset_id__icontains=search) | Q(serial_number__icontains=search) ) # Filter by category category = self.request.GET.get('category') if category: queryset = queryset.filter(category_id=category) # Filter by building building = self.request.GET.get('building') if building: queryset = queryset.filter(building_id=building) # Filter by status status = self.request.GET.get('status') if status: queryset = queryset.filter(status=status) # Filter by condition condition = self.request.GET.get('condition') if condition: queryset = queryset.filter(condition=condition) # Filter by maintenance needs maintenance_filter = self.request.GET.get('maintenance') if maintenance_filter == 'due': queryset = queryset.filter(next_maintenance_date__lte=timezone.now().date()) elif maintenance_filter == 'overdue': queryset = queryset.filter(next_maintenance_date__lt=timezone.now().date()) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['categories'] = AssetCategory.objects.filter(is_active=True) context['buildings'] = Building.objects.filter(is_active=True) context['status_choices'] = Asset.AssetStatus.choices context['condition_choices'] = Asset.AssetCondition.choices return context class AssetDetailView(LoginRequiredMixin, DetailView): model = Asset template_name = 'facility_management/assets/detail.html' context_object_name = 'asset' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) asset = self.get_object() # Maintenance history context['maintenance_history'] = MaintenanceRequest.objects.filter( asset=asset ).select_related('requested_by', 'assigned_to').order_by('-requested_date')[:10] # Asset value depreciation calculation if asset.purchase_cost and asset.purchase_date: years_owned = Decimal((timezone.now().date() - asset.purchase_date).days) / Decimal('365.25') rate = Decimal(asset.depreciation_rate) / Decimal('100') depreciated_value = asset.purchase_cost * (Decimal('1') - rate) * years_owned context['calculated_value'] = max(depreciated_value, Decimal('0.00')) return context class AssetCreateView(LoginRequiredMixin, CreateView): model = Asset form_class = AssetForm template_name = 'facility_management/assets/form.html' success_url = reverse_lazy('facility_management:asset_list') def form_valid(self, form): messages.success(self.request, 'Asset created successfully.') return super().form_valid(form) class AssetUpdateView(LoginRequiredMixin, UpdateView): model = Asset form_class = AssetForm template_name = 'facility_management/assets/form.html' success_url = reverse_lazy('facility_management:asset_list') def form_valid(self, form): messages.success(self.request, 'Asset updated successfully.') return super().form_valid(form) class MaintenanceRequestListView(LoginRequiredMixin, ListView): model = MaintenanceRequest template_name = 'facility_management/maintenance/list.html' context_object_name = 'maintenance_requests' paginate_by = 25 def get_queryset(self): queryset = MaintenanceRequest.objects.select_related( 'building', 'requested_by', 'assigned_to', 'maintenance_type' ).order_by('-requested_date') # Apply filters form = MaintenanceFilterForm(self.request.GET) if form.is_valid(): if form.cleaned_data['status']: queryset = queryset.filter(status=form.cleaned_data['status']) if form.cleaned_data['priority']: queryset = queryset.filter(priority=form.cleaned_data['priority']) if form.cleaned_data['building']: queryset = queryset.filter(building=form.cleaned_data['building']) if form.cleaned_data['assigned_to']: queryset = queryset.filter(assigned_to=form.cleaned_data['assigned_to']) if form.cleaned_data['date_from']: queryset = queryset.filter(requested_date__date__gte=form.cleaned_data['date_from']) if form.cleaned_data['date_to']: queryset = queryset.filter(requested_date__date__lte=form.cleaned_data['date_to']) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['filter_form'] = MaintenanceFilterForm(self.request.GET) return context class MaintenanceRequestDetailView(LoginRequiredMixin, DetailView): model = MaintenanceRequest template_name = 'facility_management/maintenance/detail.html' context_object_name = 'maintenance' def get_queryset(self): return MaintenanceRequest.objects.select_related( 'building', 'floor', 'room', 'asset', 'requested_by', 'assigned_to', 'maintenance_type' ) class MaintenanceRequestCreateView(LoginRequiredMixin, CreateView): model = MaintenanceRequest form_class = MaintenanceRequestForm template_name = 'facility_management/maintenance/form.html' success_url = reverse_lazy('facility_management:maintenance_list') def form_valid(self, form): form.instance.requested_by = self.request.user messages.success(self.request, 'Maintenance request created successfully.') return super().form_valid(form) class MaintenanceRequestUpdateView(LoginRequiredMixin, UpdateView): model = MaintenanceRequest form_class = MaintenanceUpdateForm template_name = 'facility_management/maintenance/form.html' success_url = reverse_lazy('facility_management:maintenance_list') def form_valid(self, form): messages.success(self.request, 'Maintenance request updated successfully.') return super().form_valid(form) class RoomListView(LoginRequiredMixin, ListView): model = Room template_name = 'facility_management/rooms/list.html' context_object_name = 'rooms' paginate_by = 25 def get_queryset(self): queryset = Room.objects.select_related('floor__building').order_by('floor__building__name', 'floor__floor_number', 'room_number') # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(room_number__icontains=search) | Q(name__icontains=search) | Q(current_tenant__icontains=search) ) # Filter by building building = self.request.GET.get('building') if building: queryset = queryset.filter(floor__building_id=building) # Filter by room type room_type = self.request.GET.get('room_type') if room_type: queryset = queryset.filter(room_type=room_type) # Filter by occupancy status occupancy = self.request.GET.get('occupancy') if occupancy: queryset = queryset.filter(occupancy_status=occupancy) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['buildings'] = Building.objects.filter(is_active=True) context['occupancy_statuses'] = Room.OccupancyStatus.choices return context class RoomDetailView(LoginRequiredMixin, DetailView): model = Room template_name = 'facility_management/rooms/detail.html' context_object_name = 'room' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) room = self.get_object() # Assets in this room context['room_assets'] = Asset.objects.filter(room=room).select_related('category') # Maintenance requests for this room context['room_maintenance'] = MaintenanceRequest.objects.filter( room=room ).select_related('requested_by', 'assigned_to').order_by('-requested_date')[:5] return context class RoomCreateView(LoginRequiredMixin, CreateView): model = Room form_class = RoomForm template_name = 'facility_management/rooms/form.html' success_url = reverse_lazy('facility_management:room_list') def form_valid(self, form): messages.success(self.request, 'Room created successfully.') return super().form_valid(form) class RoomUpdateView(LoginRequiredMixin, UpdateView): model = Room form_class = RoomForm template_name = 'facility_management/rooms/form.html' success_url = reverse_lazy('facility_management:room_list') def form_valid(self, form): messages.success(self.request, 'Room updated successfully.') return super().form_valid(form) class VendorListView(LoginRequiredMixin, ListView): model = Vendor template_name = 'facility_management/vendors/list.html' context_object_name = 'vendors' paginate_by = 20 def get_queryset(self): queryset = Vendor.objects.order_by('name') # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(name__icontains=search) | Q(contact_person__icontains=search) | Q(email__icontains=search) ) # Filter by vendor type vendor_type = self.request.GET.get('vendor_type') if vendor_type: queryset = queryset.filter(vendor_type=vendor_type) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['vendor_types'] = Vendor.VendorType.choices return context class VendorDetailView(LoginRequiredMixin, DetailView): model = Vendor template_name = 'facility_management/vendors/detail.html' context_object_name = 'vendor' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) vendor = self.get_object() # Active contracts context['active_contracts'] = ServiceContract.objects.filter( vendor=vendor, status='active' ).order_by('-start_date') # Contract history context['contract_history'] = ServiceContract.objects.filter( vendor=vendor ).order_by('-start_date')[:10] return context class VendorCreateView(LoginRequiredMixin, CreateView): model = Vendor form_class = VendorForm template_name = 'facility_management/vendors/form.html' success_url = reverse_lazy('facility_management:vendor_list') def form_valid(self, form): messages.success(self.request, 'Vendor created successfully.') return super().form_valid(form) class VendorUpdateView(LoginRequiredMixin, UpdateView): model = Vendor form_class = VendorForm template_name = 'facility_management/vendors/form.html' success_url = reverse_lazy('facility_management:vendor_list') def form_valid(self, form): messages.success(self.request, 'Vendor updated successfully.') return super().form_valid(form) class ServiceContractListView(LoginRequiredMixin, ListView): model = ServiceContract template_name = 'facility_management/contracts/list.html' context_object_name = 'contracts' paginate_by = 20 def get_queryset(self): return ServiceContract.objects.select_related('vendor').order_by('-start_date') class ServiceContractDetailView(LoginRequiredMixin, DetailView): model = ServiceContract template_name = 'facility_management/contracts/detail.html' context_object_name = 'contract' class ServiceContractCreateView(LoginRequiredMixin, CreateView): model = ServiceContract form_class = ServiceContractForm template_name = 'facility_management/contracts/form.html' success_url = reverse_lazy('facility_management:contract_list') def form_valid(self, form): messages.success(self.request, 'Service contract created successfully.') return super().form_valid(form) class ServiceContractUpdateView(LoginRequiredMixin, UpdateView): model = ServiceContract form_class = ServiceContractForm template_name = 'facility_management/contracts/form.html' success_url = reverse_lazy('facility_management:contract_list') def form_valid(self, form): messages.success(self.request, 'Service contract updated successfully.') return super().form_valid(form) class FacilityReportView(LoginRequiredMixin, TemplateView): template_name = 'facility_management/reports/facility.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) today = timezone.now().date() # Buildings context['buildings'] = Building.objects.filter(is_active=True) context['total_buildings'] = Building.objects.count() context['active_buildings'] = Building.objects.filter(is_active=True).count() # Assets context['total_assets'] = Asset.objects.count() context['operational_assets'] = Asset.objects.filter(status=Asset.AssetStatus.OPERATIONAL).count() # Asset statistics context['asset_stats'] = { 'by_category': AssetCategory.objects.annotate( asset_count=Count('asset') ).order_by('-asset_count'), 'by_condition': Asset.objects.values('condition').annotate( count=Count('id') ).order_by('condition'), 'by_status': Asset.objects.values('status').annotate( count=Count('id') ).order_by('status'), } # Maintenance context['pending_maintenance'] = MaintenanceRequest.objects.filter( status__in=[ MaintenanceRequest.MaintenanceStatus.SUBMITTED, MaintenanceRequest.MaintenanceStatus.ASSIGNED ] ).count() context['overdue_maintenance'] = MaintenanceRequest.objects.filter( scheduled_date__lt=timezone.now(), status__in=[ MaintenanceRequest.MaintenanceStatus.SUBMITTED, MaintenanceRequest.MaintenanceStatus.ASSIGNED ] ).count() # Maintenance statistics context['maintenance_stats'] = { 'by_status': MaintenanceRequest.objects.values('status').annotate( count=Count('id') ).order_by('status'), 'by_priority': MaintenanceRequest.objects.values('priority').annotate( count=Count('id') ).order_by('priority'), 'by_type': MaintenanceRequest.objects.values('maintenance_type__name').annotate( count=Count('id') ).order_by('-count')[:5], 'monthly_costs': MaintenanceRequest.objects.filter( completed_date__gte=today.replace(day=1) ).aggregate( total=Sum('actual_cost'), avg=Avg('actual_cost'), count=Count('id') ), } # Maintenance trends (last 6 months) context['maintenance_trends'] = { 'completed': [12, 15, 18, 14, 16, 20], # Placeholder data 'pending': [5, 4, 6, 3, 5, 4] # Placeholder data } # Performance metrics context['avg_completion_time'] = 3.5 # Placeholder context['on_time_completion'] = 85 # Placeholder # Contracts context['active_contracts'] = ServiceContract.objects.filter( status=ServiceContract.ContractStatus.ACTIVE ).count() context['total_contract_value'] = ServiceContract.objects.filter( status=ServiceContract.ContractStatus.ACTIVE ).aggregate(total=Sum('contract_value'))['total'] or 0 # Space utilization total_rooms = Room.objects.count() occupied_rooms = Room.objects.filter(occupancy_status=Room.OccupancyStatus.OCCUPIED).count() context['space_stats'] = { 'total_rooms': total_rooms, 'occupied_rooms': occupied_rooms, 'by_type': Room.objects.values('occupancy_status').annotate( count=Count('id') ).order_by('-count'), } context['total_rooms'] = total_rooms context['occupied_rooms'] = occupied_rooms context['available_rooms'] = Room.objects.filter(occupancy_status=Room.OccupancyStatus.VACANT).count() context['maintenance_rooms'] = Room.objects.filter(occupancy_status=Room.OccupancyStatus.MAINTENANCE).count() context['reserved_rooms'] = Room.objects.filter(occupancy_status=Room.OccupancyStatus.RESERVED).count() # Financial overview context['total_maintenance_cost'] = MaintenanceRequest.objects.filter( status=MaintenanceRequest.MaintenanceStatus.COMPLETED ).aggregate(total=Sum('actual_cost'))['total'] or 0 context['cost_breakdown'] = { 'maintenance': context['total_maintenance_cost'], 'utilities': 0, # Placeholder 'contracts': context['total_contract_value'], 'supplies': 0, # Placeholder 'other': 0, # Placeholder } # Vendor performance context['vendor_performance'] = Vendor.objects.filter(is_active=True).annotate( active_contracts_count=Count('servicecontract', filter=Q(servicecontract__status=ServiceContract.ContractStatus.ACTIVE)), total_contract_value=Sum('servicecontract__contract_value', filter=Q(servicecontract__status=ServiceContract.ContractStatus.ACTIVE)) )[:10] # KPIs context['facility_utilization'] = round((occupied_rooms / total_rooms * 100) if total_rooms > 0 else 0, 1) context['maintenance_efficiency'] = 75 # Placeholder context['cost_per_sqft'] = 0 # Placeholder context['energy_efficiency'] = 80 # Placeholder context['budget_utilization'] = 65 # Placeholder # Report metadata context['report_date'] = timezone.now() context['report_start_date'] = today - timedelta(days=30) context['report_end_date'] = today context['report_period_days'] = 30 return context @login_required def get_buildings(request): """Get building data via AJAX""" buildings = Building.objects.filter(is_active=True).values('id', 'name') return JsonResponse({'buildings': list(buildings)}) @login_required def get_floors_by_building(request): """Get floors for a specific building via AJAX""" building_id = request.GET.get('building_id') if building_id: floors = Floor.objects.filter(building_id=building_id).values('id', 'name') return JsonResponse({'floors': list(floors)}) return JsonResponse({'floors': []}) @login_required def get_rooms_by_floor(request): """Get rooms for a specific floor via AJAX""" floor_id = request.GET.get('floor_id') if floor_id: rooms = Room.objects.filter(floor_id=floor_id).values('id', 'room_number', 'name') return JsonResponse({'rooms': list(rooms)}) return JsonResponse({'rooms': []}) @login_required def get_assets_by_location(request): """Get assets for a specific location via AJAX""" building_id = request.GET.get('building_id') floor_id = request.GET.get('floor_id') room_id = request.GET.get('room_id') assets = Asset.objects.all() if building_id: assets = assets.filter(building_id=building_id) if floor_id: assets = assets.filter(floor_id=floor_id) if room_id: assets = assets.filter(room_id=room_id) asset_data = assets.values('id', 'asset_id', 'name') return JsonResponse({'assets': list(asset_data)}) @login_required def export_assets_csv(request): """Export assets data to CSV""" response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="assets_report.csv"' writer = csv.writer(response) writer.writerow([ 'Asset ID', 'Name', 'Category', 'Building', 'Status', 'Condition', 'Purchase Date', 'Purchase Cost', 'Current Value', 'Last Inspection' ]) assets = Asset.objects.select_related('category', 'building').order_by('asset_id') for asset in assets: writer.writerow([ asset.asset_id, asset.name, asset.category.name, asset.building.name, asset.get_status_display(), asset.get_condition_display(), asset.purchase_date, asset.purchase_cost, asset.current_value, asset.last_inspection_date ]) return response # ============================================================================ # INSPECTION VIEWS # ============================================================================ class InspectionListView(LoginRequiredMixin, ListView): model = Inspection template_name = 'facility_management/inspections/list.html' context_object_name = 'inspections' paginate_by = 25 def get_queryset(self): queryset = Inspection.objects.select_related( 'building', 'inspector' ).order_by('-scheduled_date') # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(inspection_id__icontains=search) | Q(title__icontains=search) | Q(inspector_external__icontains=search) ) # Filter by inspection type inspection_type = self.request.GET.get('inspection_type') if inspection_type: queryset = queryset.filter(inspection_type=inspection_type) # Filter by status status = self.request.GET.get('status') if status: queryset = queryset.filter(status=status) # Filter by building building = self.request.GET.get('building') if building: queryset = queryset.filter(building_id=building) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['inspection_types'] = Inspection.InspectionType.choices context['status_choices'] = Inspection.Status.choices context['buildings'] = Building.objects.filter(is_active=True) return context class InspectionDetailView(LoginRequiredMixin, DetailView): model = Inspection template_name = 'facility_management/inspections/detail.html' context_object_name = 'inspection' def get_queryset(self): return Inspection.objects.select_related( 'building', 'inspector' ).prefetch_related('floors', 'rooms', 'assets') class InspectionCreateView(LoginRequiredMixin, CreateView): model = Inspection form_class = InspectionForm template_name = 'facility_management/inspections/form.html' success_url = reverse_lazy('facility_management:inspection_list') def form_valid(self, form): messages.success(self.request, 'Inspection created successfully.') return super().form_valid(form) class InspectionUpdateView(LoginRequiredMixin, UpdateView): model = Inspection form_class = InspectionForm template_name = 'facility_management/inspections/form.html' success_url = reverse_lazy('facility_management:inspection_list') def form_valid(self, form): messages.success(self.request, 'Inspection updated successfully.') return super().form_valid(form) # ============================================================================ # ENERGY METER VIEWS # ============================================================================ class EnergyMeterListView(LoginRequiredMixin, ListView): model = EnergyMeter template_name = 'facility_management/energy_meters/list.html' context_object_name = 'meters' paginate_by = 25 def get_queryset(self): queryset = EnergyMeter.objects.select_related('building').order_by('building', 'meter_type', 'meter_id') # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(meter_id__icontains=search) | Q(serial_number__icontains=search) | Q(location_description__icontains=search) ) # Filter by meter type meter_type = self.request.GET.get('meter_type') if meter_type: queryset = queryset.filter(meter_type=meter_type) # Filter by building building = self.request.GET.get('building') if building: queryset = queryset.filter(building_id=building) # Filter by active status is_active = self.request.GET.get('is_active') if is_active: queryset = queryset.filter(is_active=is_active == 'true') # Filter by calibration status calibration = self.request.GET.get('calibration') if calibration == 'due': queryset = queryset.filter(next_calibration_date__lte=timezone.now().date()) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['meter_types'] = EnergyMeter.MeterType.choices context['buildings'] = Building.objects.filter(is_active=True) return context class EnergyMeterDetailView(LoginRequiredMixin, DetailView): model = EnergyMeter template_name = 'facility_management/energy_meters/detail.html' context_object_name = 'meter' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) meter = self.get_object() # Recent readings context['recent_readings'] = EnergyReading.objects.filter( meter=meter ).select_related('read_by').order_by('-reading_date')[:10] # Calculate consumption trends last_30_days = timezone.now() - timedelta(days=30) context['monthly_consumption'] = EnergyReading.objects.filter( meter=meter, reading_date__gte=last_30_days ).aggregate( total_consumption=Sum('consumption'), total_cost=Sum('cost'), avg_consumption=Avg('consumption') ) return context class EnergyMeterCreateView(LoginRequiredMixin, CreateView): model = EnergyMeter form_class = EnergyMeterForm template_name = 'facility_management/energy_meters/form.html' success_url = reverse_lazy('facility_management:energy_meter_list') def form_valid(self, form): messages.success(self.request, 'Energy meter created successfully.') return super().form_valid(form) class EnergyMeterUpdateView(LoginRequiredMixin, UpdateView): model = EnergyMeter form_class = EnergyMeterForm template_name = 'facility_management/energy_meters/form.html' success_url = reverse_lazy('facility_management:energy_meter_list') def form_valid(self, form): messages.success(self.request, 'Energy meter updated successfully.') return super().form_valid(form) # ============================================================================ # ENERGY READING VIEWS # ============================================================================ class EnergyReadingListView(LoginRequiredMixin, ListView): model = EnergyReading template_name = 'facility_management/energy_readings/list.html' context_object_name = 'readings' paginate_by = 50 def get_queryset(self): queryset = EnergyReading.objects.select_related( 'meter__building', 'read_by' ).order_by('-reading_date') # Filter by meter meter = self.request.GET.get('meter') if meter: queryset = queryset.filter(meter_id=meter) # Filter by date range date_from = self.request.GET.get('date_from') if date_from: queryset = queryset.filter(reading_date__date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(reading_date__date__lte=date_to) # Filter by estimated readings is_estimated = self.request.GET.get('is_estimated') if is_estimated: queryset = queryset.filter(is_estimated=is_estimated == 'true') return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['meters'] = EnergyMeter.objects.filter(is_active=True).select_related('building') # Calculate summary statistics queryset = self.get_queryset() context['summary'] = queryset.aggregate( total_consumption=Sum('consumption'), total_cost=Sum('cost'), avg_consumption=Avg('consumption'), count=Count('id') ) return context class EnergyReadingDetailView(LoginRequiredMixin, DetailView): model = EnergyReading template_name = 'facility_management/energy_readings/detail.html' context_object_name = 'reading' def get_queryset(self): return EnergyReading.objects.select_related('meter__building', 'read_by') class EnergyReadingCreateView(LoginRequiredMixin, CreateView): model = EnergyReading form_class = EnergyReadingForm template_name = 'facility_management/energy_readings/form.html' success_url = reverse_lazy('facility_management:energy_reading_list') def form_valid(self, form): form.instance.read_by = self.request.user messages.success(self.request, 'Energy reading recorded successfully.') return super().form_valid(form) class EnergyReadingUpdateView(LoginRequiredMixin, UpdateView): model = EnergyReading form_class = EnergyReadingForm template_name = 'facility_management/energy_readings/form.html' success_url = reverse_lazy('facility_management:energy_reading_list') def form_valid(self, form): messages.success(self.request, 'Energy reading updated successfully.') return super().form_valid(form) # ============================================================================ # SPACE RESERVATION VIEWS # ============================================================================ class SpaceReservationListView(LoginRequiredMixin, ListView): model = SpaceReservation template_name = 'facility_management/reservations/list.html' context_object_name = 'reservations' paginate_by = 25 def get_queryset(self): queryset = SpaceReservation.objects.select_related( 'room__floor__building', 'reserved_by', 'approved_by' ).order_by('-start_datetime') # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(title__icontains=search) | Q(contact_person__icontains=search) | Q(contact_email__icontains=search) ) # Filter by room room = self.request.GET.get('room') if room: queryset = queryset.filter(room_id=room) # Filter by status status = self.request.GET.get('status') if status: queryset = queryset.filter(status=status) # Filter by date range date_from = self.request.GET.get('date_from') if date_from: queryset = queryset.filter(start_datetime__date__gte=date_from) date_to = self.request.GET.get('date_to') if date_to: queryset = queryset.filter(start_datetime__date__lte=date_to) # Filter by user's reservations my_reservations = self.request.GET.get('my_reservations') if my_reservations: queryset = queryset.filter(reserved_by=self.request.user) return queryset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['rooms'] = Room.objects.select_related('floor__building') context['status_choices'] = SpaceReservation.ReservationStatus.choices # Upcoming reservations context['upcoming_count'] = SpaceReservation.objects.filter( start_datetime__gte=timezone.now(), status__in=['PENDING', 'CONFIRMED'] ).count() return context class SpaceReservationDetailView(LoginRequiredMixin, DetailView): model = SpaceReservation template_name = 'facility_management/reservations/detail.html' context_object_name = 'reservation' def get_queryset(self): return SpaceReservation.objects.select_related( 'room__floor__building', 'reserved_by', 'approved_by' ) class SpaceReservationCreateView(LoginRequiredMixin, CreateView): model = SpaceReservation form_class = SpaceReservationForm template_name = 'facility_management/reservations/form.html' success_url = reverse_lazy('facility_management:reservation_list') def form_valid(self, form): form.instance.reserved_by = self.request.user messages.success(self.request, 'Space reservation created successfully.') return super().form_valid(form) class SpaceReservationUpdateView(LoginRequiredMixin, UpdateView): model = SpaceReservation form_class = SpaceReservationForm template_name = 'facility_management/reservations/form.html' success_url = reverse_lazy('facility_management:reservation_list') def form_valid(self, form): messages.success(self.request, 'Space reservation updated successfully.') return super().form_valid(form) @login_required def approve_reservation(request, pk): """Approve a space reservation""" reservation = get_object_or_404(SpaceReservation, pk=pk) if request.method == 'POST': reservation.status = 'CONFIRMED' reservation.approved_by = request.user reservation.approved_at = timezone.now() reservation.save() messages.success(request, f'Reservation "{reservation.title}" approved successfully.') return redirect('facility_management:reservation_detail', pk=pk) @login_required def cancel_reservation(request, pk): """Cancel a space reservation""" reservation = get_object_or_404(SpaceReservation, pk=pk) if request.method == 'POST': reservation.status = 'CANCELLED' reservation.save() messages.success(request, f'Reservation "{reservation.title}" cancelled successfully.') return redirect('facility_management:reservation_detail', pk=pk) @login_required def export_inspections_csv(request): """Export inspections to CSV""" response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="inspections_report.csv"' writer = csv.writer(response) writer.writerow([ 'Inspection ID', 'Type', 'Title', 'Building', 'Status', 'Scheduled Date', 'Inspector', 'Overall Rating', 'Requires Follow-up' ]) inspections = Inspection.objects.select_related('building', 'inspector').order_by('-scheduled_date') for inspection in inspections: writer.writerow([ inspection.inspection_id, inspection.get_inspection_type_display(), inspection.title, inspection.building.name, inspection.get_status_display(), inspection.scheduled_date.strftime('%Y-%m-%d %H:%M'), inspection.inspector.get_full_name() if inspection.inspector else inspection.inspector_external, inspection.overall_rating, 'Yes' if inspection.requires_followup else 'No' ]) return response @login_required def export_energy_readings_csv(request): """Export energy readings to CSV""" response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="energy_readings_report.csv"' writer = csv.writer(response) writer.writerow([ 'Meter ID', 'Meter Type', 'Building', 'Reading Date', 'Reading Value', 'Consumption', 'Cost', 'Is Estimated', 'Read By' ]) readings = EnergyReading.objects.select_related( 'meter__building', 'read_by' ).order_by('-reading_date') for reading in readings: writer.writerow([ reading.meter.meter_id, reading.meter.get_meter_type_display(), reading.meter.building.name, reading.reading_date.strftime('%Y-%m-%d %H:%M'), reading.reading_value, reading.consumption, reading.cost, 'Yes' if reading.is_estimated else 'No', reading.read_by.get_full_name() ]) return response @login_required def export_reservations_csv(request): """Export space reservations to CSV""" response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="reservations_report.csv"' writer = csv.writer(response) writer.writerow([ 'Reservation ID', 'Room', 'Title', 'Start Date', 'End Date', 'Status', 'Reserved By', 'Contact Person', 'Expected Attendees', 'Total Cost', 'Approved By' ]) reservations = SpaceReservation.objects.select_related( 'room__floor__building', 'reserved_by', 'approved_by' ).order_by('-start_datetime') for reservation in reservations: writer.writerow([ reservation.reservation_id, f"{reservation.room.floor.building.code}-{reservation.room.floor.name}-{reservation.room.room_number}", reservation.title, reservation.start_datetime.strftime('%Y-%m-%d %H:%M'), reservation.end_datetime.strftime('%Y-%m-%d %H:%M'), reservation.get_status_display(), reservation.reserved_by.get_full_name(), reservation.contact_person, reservation.expected_attendees, reservation.total_cost, reservation.approved_by.get_full_name() if reservation.approved_by else '' ]) return response @login_required def export_maintenance_csv(request): """Export maintenance requests to CSV""" response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="maintenance_report.csv"' writer = csv.writer(response) writer.writerow([ 'Request ID', 'Title', 'Building', 'Priority', 'Status', 'Requested Date', 'Requested By', 'Assigned To', 'Estimated Cost', 'Actual Cost' ]) requests = MaintenanceRequest.objects.select_related( 'building', 'requested_by', 'assigned_to' ).order_by('-requested_date') for req in requests: writer.writerow([ req.request_id, req.title, req.building.name, req.get_priority_display(), req.get_status_display(), req.requested_date.strftime('%Y-%m-%d %H:%M:%S'), req.requested_by.get_full_name(), req.assigned_to.get_full_name() if req.assigned_to else '', req.estimated_cost, req.actual_cost ]) return response