Marwan Alwali ab2c4a36c5 update
2025-10-02 10:13:03 +03:00

702 lines
25 KiB
Python

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=['SUBMITTED', 'ASSIGNED', 'IN_PROGRESS']).count(),
'completed_today': MaintenanceRequest.objects.filter(completed_date__date=today).count(),
'total_rooms': Room.objects.count(),
'occupied_rooms': Room.objects.filter(occupancy_status='OCCUPIED').count(),
'active_contracts': ServiceContract.objects.filter(status='ACTIVE').count(),
'contracts_expiring_soon': ServiceContract.objects.filter(
end_date__lte=today + timedelta(days=30),
status='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='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='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)
# 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 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'),
'monthly_costs': MaintenanceRequest.objects.filter(
completed_date__gte=timezone.now().date().replace(day=1)
).aggregate(
total=Sum('actual_cost'),
avg=Avg('actual_cost'),
count=Count('id')
),
}
# Space utilization
context['space_stats'] = {
'total_rooms': Room.objects.count(),
'occupied_rooms': Room.objects.filter(occupancy_status='occupied').count(),
'vacant_rooms': Room.objects.filter(occupancy_status='vacant').count(),
'maintenance_rooms': Room.objects.filter(occupancy_status='maintenance').count(),
}
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
@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