449 lines
21 KiB
Python
449 lines
21 KiB
Python
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils import timezone
|
|
from decimal import Decimal
|
|
from .models import *
|
|
from accounts.models import User
|
|
|
|
|
|
class BuildingForm(forms.ModelForm):
|
|
"""Form for managing buildings"""
|
|
|
|
class Meta:
|
|
model = Building
|
|
fields = [
|
|
'name', 'code', 'building_type', 'address',
|
|
'floor_count', 'total_area_sqm', 'construction_year',
|
|
'architect', 'contractor', 'facility_manager',
|
|
'last_major_renovation', 'is_active'
|
|
]
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'code': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'building_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'airport_code': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'floor_count': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
|
|
'total_area_sqm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
|
|
'construction_year': forms.NumberInput(attrs={'class': 'form-control', 'min': '1900', 'max': '2030'}),
|
|
'architect': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'contractor': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'facility_manager': forms.Select(attrs={'class': 'form-select'}),
|
|
'last_major_renovation': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
def clean_construction_year(self):
|
|
year = self.cleaned_data.get('construction_year')
|
|
if year and year > timezone.now().year:
|
|
raise ValidationError('Construction year cannot be in the future.')
|
|
return year
|
|
|
|
|
|
class FloorForm(forms.ModelForm):
|
|
"""Form for managing floors"""
|
|
|
|
class Meta:
|
|
model = Floor
|
|
fields = [
|
|
'building', 'floor_number', 'name', 'area_sqm',
|
|
'ceiling_height_m', 'is_public_access'
|
|
]
|
|
widgets = {
|
|
'building': forms.Select(attrs={'class': 'form-select'}),
|
|
'floor_number': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'area_sqm': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
|
|
'ceiling_height_m': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
|
|
'is_public_access': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['building'].queryset = Building.objects.filter(is_active=True)
|
|
|
|
|
|
class RoomForm(forms.ModelForm):
|
|
"""Form for managing rooms"""
|
|
|
|
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'
|
|
]
|
|
widgets = {
|
|
'floor': forms.Select(attrs={'class': 'form-select'}),
|
|
'room_number': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'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
|
|
|
|
|
|
class AssetCategoryForm(forms.ModelForm):
|
|
"""Form for managing asset categories"""
|
|
|
|
class Meta:
|
|
model = AssetCategory
|
|
fields = ['name', 'code', 'description', 'parent_category', 'is_active']
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'code': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'parent_category': forms.Select(attrs={'class': 'form-select'}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Exclude self from parent category choices
|
|
if self.instance.pk:
|
|
self.fields['parent_category'].queryset = AssetCategory.objects.exclude(pk=self.instance.pk)
|
|
|
|
|
|
class AssetForm(forms.ModelForm):
|
|
"""Form for managing assets"""
|
|
|
|
class Meta:
|
|
model = Asset
|
|
fields = [
|
|
'asset_id', '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',
|
|
'service_contract_number', 'status', 'condition',
|
|
'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'}),
|
|
'floor': forms.Select(attrs={'class': 'form-select'}),
|
|
'room': forms.Select(attrs={'class': 'form-select'}),
|
|
'location_description': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'manufacturer': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'model': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'serial_number': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'purchase_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'purchase_cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
|
|
'current_value': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
|
|
'depreciation_rate': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0', 'max': '100'}),
|
|
'warranty_start_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'warranty_end_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'service_provider': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'service_contract_number': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'condition': forms.Select(attrs={'class': 'form-select'}),
|
|
'last_inspection_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'next_maintenance_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'assigned_to': forms.Select(attrs={'class': 'form-select'}),
|
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['category'].queryset = AssetCategory.objects.filter(is_active=True)
|
|
self.fields['building'].queryset = Building.objects.filter(is_active=True)
|
|
self.fields['floor'].required = False
|
|
self.fields['room'].required = False
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
warranty_start = cleaned_data.get('warranty_start_date')
|
|
warranty_end = cleaned_data.get('warranty_end_date')
|
|
purchase_date = cleaned_data.get('purchase_date')
|
|
purchase_cost = cleaned_data.get('purchase_cost')
|
|
current_value = cleaned_data.get('current_value')
|
|
|
|
if warranty_start and warranty_end and warranty_start >= warranty_end:
|
|
raise ValidationError('Warranty end date must be after warranty start date.')
|
|
|
|
if purchase_date and warranty_start and warranty_start < purchase_date:
|
|
raise ValidationError('Warranty start date cannot be before purchase date.')
|
|
|
|
if purchase_cost and current_value and current_value > purchase_cost:
|
|
raise ValidationError('Current value cannot be greater than purchase cost.')
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class MaintenanceRequestForm(forms.ModelForm):
|
|
"""Form for creating maintenance requests"""
|
|
|
|
class Meta:
|
|
model = MaintenanceRequest
|
|
fields = [
|
|
'title', 'description', 'maintenance_type', 'building',
|
|
'floor', 'room', 'asset', 'priority', 'scheduled_date', 'actual_cost',
|
|
'estimated_cost', 'notes', 'assigned_to', 'scheduled_date', 'estimated_hours', 'status',
|
|
]
|
|
widgets = {
|
|
'title': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
|
'maintenance_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'building': forms.Select(attrs={'class': 'form-select'}),
|
|
'floor': forms.Select(attrs={'class': 'form-select'}),
|
|
'room': forms.Select(attrs={'class': 'form-select'}),
|
|
'asset': forms.Select(attrs={'class': 'form-select'}),
|
|
'priority': forms.Select(attrs={'class': 'form-select'}),
|
|
'scheduled_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
|
|
'estimated_cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
|
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'assigned_to': forms.Select(attrs={'class': 'form-select'}),
|
|
'estimated_hours': forms.NumberInput(attrs={'class': 'form-control', 'type': 'number'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'actual_cost': forms.NumberInput(attrs={'class': 'form-control', 'type': 'number'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['maintenance_type'].queryset = MaintenanceType.objects.filter(is_active=True)
|
|
self.fields['building'].queryset = Building.objects.filter(is_active=True)
|
|
self.fields['floor'].required = False
|
|
self.fields['room'].required = False
|
|
self.fields['asset'].required = False
|
|
|
|
|
|
class MaintenanceUpdateForm(forms.ModelForm):
|
|
"""Form for updating maintenance requests"""
|
|
|
|
class Meta:
|
|
model = MaintenanceRequest
|
|
fields = [
|
|
'status', 'assigned_to', 'scheduled_date', 'started_date',
|
|
'completed_date', 'actual_cost', 'completion_notes'
|
|
]
|
|
widgets = {
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'assigned_to': forms.Select(attrs={'class': 'form-select'}),
|
|
'scheduled_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
|
|
'started_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
|
|
'completed_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
|
|
'actual_cost': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
|
|
'completion_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
|
}
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
scheduled_date = cleaned_data.get('scheduled_date')
|
|
started_date = cleaned_data.get('started_date')
|
|
completed_date = cleaned_data.get('completed_date')
|
|
|
|
if started_date and scheduled_date and started_date < scheduled_date:
|
|
raise ValidationError('Start date cannot be before scheduled date.')
|
|
|
|
if completed_date and started_date and completed_date < started_date:
|
|
raise ValidationError('Completion date cannot be before start date.')
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class MaintenanceScheduleForm(forms.ModelForm):
|
|
"""Form for creating maintenance schedules"""
|
|
|
|
class Meta:
|
|
model = MaintenanceSchedule
|
|
fields = [
|
|
'name', 'description', 'maintenance_type', 'asset', 'building',
|
|
'room', 'frequency', 'frequency_interval', 'start_date',
|
|
'end_date', 'assigned_to', 'estimated_duration_hours', 'is_active'
|
|
]
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'maintenance_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'asset': forms.Select(attrs={'class': 'form-select'}),
|
|
'building': forms.Select(attrs={'class': 'form-select'}),
|
|
'room': forms.Select(attrs={'class': 'form-select'}),
|
|
'frequency': forms.Select(attrs={'class': 'form-select'}),
|
|
'frequency_interval': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
|
|
'start_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'end_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'assigned_to': forms.Select(attrs={'class': 'form-select'}),
|
|
'estimated_duration_hours': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.5', 'min': '0'}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['maintenance_type'].queryset = MaintenanceType.objects.filter(is_active=True)
|
|
self.fields['asset'].required = False
|
|
self.fields['building'].required = False
|
|
self.fields['room'].required = False
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
start_date = cleaned_data.get('start_date')
|
|
end_date = cleaned_data.get('end_date')
|
|
asset = cleaned_data.get('asset')
|
|
building = cleaned_data.get('building')
|
|
room = cleaned_data.get('room')
|
|
|
|
if start_date and end_date and start_date >= end_date:
|
|
raise ValidationError('End date must be after start date.')
|
|
|
|
# At least one scope must be specified
|
|
if not any([asset, building, room]):
|
|
raise ValidationError('Please specify at least one scope: asset, building, or room.')
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class VendorForm(forms.ModelForm):
|
|
"""Form for managing vendors"""
|
|
|
|
class Meta:
|
|
model = Vendor
|
|
fields = [
|
|
'name', 'vendor_type', 'contact_person', 'email', 'phone',
|
|
'address', 'license_number', 'insurance_policy',
|
|
'insurance_expiry', 'rating', 'is_active'
|
|
]
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'vendor_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'contact_person': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'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'}),
|
|
'rating': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1', 'min': '0', 'max': '5'}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
|
|
class ServiceContractForm(forms.ModelForm):
|
|
"""Form for managing service contracts"""
|
|
|
|
class Meta:
|
|
model = ServiceContract
|
|
fields = [
|
|
'contract_number', 'vendor', 'title', 'description',
|
|
'start_date', 'end_date', 'contract_value', 'payment_terms',
|
|
'buildings', 'service_areas', 'auto_renewal', 'renewal_notice_days'
|
|
]
|
|
widgets = {
|
|
'contract_number': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'vendor': forms.Select(attrs={'class': 'form-select'}),
|
|
'title': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
|
'start_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'end_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
|
'contract_value': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
|
|
'payment_terms': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'buildings': forms.CheckboxSelectMultiple(),
|
|
'service_areas': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'auto_renewal': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'renewal_notice_days': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['vendor'].queryset = Vendor.objects.filter(is_active=True)
|
|
self.fields['buildings'].queryset = Building.objects.filter(is_active=True)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
start_date = cleaned_data.get('start_date')
|
|
end_date = cleaned_data.get('end_date')
|
|
|
|
if start_date and end_date and start_date >= end_date:
|
|
raise ValidationError('End date must be after start date.')
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class FacilitySearchForm(forms.Form):
|
|
"""Form for searching facilities"""
|
|
search_query = forms.CharField(
|
|
max_length=100,
|
|
required=False,
|
|
widget=forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Search buildings, rooms, assets...'
|
|
})
|
|
)
|
|
building = forms.ModelChoiceField(
|
|
queryset=Building.objects.filter(is_active=True),
|
|
required=False,
|
|
empty_label="All Buildings",
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
asset_category = forms.ModelChoiceField(
|
|
queryset=AssetCategory.objects.filter(is_active=True),
|
|
required=False,
|
|
empty_label="All Categories",
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
|
|
|
|
class MaintenanceFilterForm(forms.Form):
|
|
"""Form for filtering maintenance requests"""
|
|
STATUS_CHOICES = [('', 'All Statuses')] + MaintenanceRequest.MaintenanceStatus.choices
|
|
PRIORITY_CHOICES = [('', 'All Priorities')] + MaintenanceRequest.Priority.choices
|
|
|
|
status = forms.ChoiceField(
|
|
choices=STATUS_CHOICES,
|
|
required=False,
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
priority = forms.ChoiceField(
|
|
choices=PRIORITY_CHOICES,
|
|
required=False,
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
building = forms.ModelChoiceField(
|
|
queryset=Building.objects.filter(is_active=True),
|
|
required=False,
|
|
empty_label="All Buildings",
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
assigned_to = forms.ModelChoiceField(
|
|
queryset=None, # Will be set in __init__
|
|
required=False,
|
|
empty_label="All Assignees",
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
date_from = forms.DateField(
|
|
required=False,
|
|
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
|
)
|
|
date_to = forms.DateField(
|
|
required=False,
|
|
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.fields['assigned_to'].queryset = User.objects.filter(is_active=True)
|
|
|