Dodaj detalje dizalice (proizvođač, model i SN) te vlasnika u modal radnog zadatka i kalendar zadataka, uz zajednički frontend helper za DRY prikaz. Proširi Task API serializer dodatnim poljima za prikaz dizalice/vlasnika i ažuriraj testove serializera za nova izlazna polja. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
214 lines
7.5 KiB
Python
214 lines
7.5 KiB
Python
# backend/modules/task_management/serializers.py
|
|
|
|
from rest_framework import serializers
|
|
from .models import Task, TaskTemplate, TaskTemplateEntry, TaskWorkEntry, TaskWorkHoursTable
|
|
|
|
|
|
WORK_HOURS_ROW_FIELDS = [
|
|
'day',
|
|
'date',
|
|
'work_time_from',
|
|
'work_time_to',
|
|
'travel_time_from',
|
|
'travel_time_to',
|
|
'break_hours',
|
|
'work_hours',
|
|
'travel_hours',
|
|
'departure_place',
|
|
'arrival_place',
|
|
'vehicle_km',
|
|
]
|
|
|
|
|
|
class TaskWorkHoursTableSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TaskWorkHoursTable
|
|
fields = ['id', 'task', 'data', 'created_at', 'updated_at']
|
|
read_only_fields = ['id', 'created_at', 'updated_at']
|
|
|
|
def _normalize_row(self, row):
|
|
normalized = {}
|
|
for field in WORK_HOURS_ROW_FIELDS:
|
|
value = row.get(field, '')
|
|
normalized[field] = '' if value is None else str(value).strip()
|
|
return normalized
|
|
|
|
def validate_data(self, value):
|
|
if value is None:
|
|
return None
|
|
if not isinstance(value, dict):
|
|
raise serializers.ValidationError("Podaci tablice moraju biti JSON objekt ili null.")
|
|
|
|
rows = value.get('rows', [])
|
|
if not isinstance(rows, list):
|
|
raise serializers.ValidationError({"rows": "Polje 'rows' mora biti lista redaka."})
|
|
|
|
normalized_rows = []
|
|
for row in rows[:30]:
|
|
if not isinstance(row, dict):
|
|
raise serializers.ValidationError({"rows": "Svaki redak mora biti JSON objekt."})
|
|
normalized_rows.append(self._normalize_row(row))
|
|
|
|
return {'rows': normalized_rows}
|
|
|
|
|
|
class TaskWorkEntrySerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TaskWorkEntry
|
|
fields = [
|
|
'id', 'task', 'template_entry', 'sort_order', 'title',
|
|
'work_description', 'active_faults', 'explanation_hr',
|
|
'created_at',
|
|
]
|
|
read_only_fields = ['id', 'created_at']
|
|
|
|
|
|
class TaskTemplateEntrySerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = TaskTemplateEntry
|
|
fields = [
|
|
'id', 'sort_order', 'title', 'work_description',
|
|
'active_faults', 'explanation_hr',
|
|
]
|
|
read_only_fields = ['id']
|
|
|
|
|
|
class TaskTemplateSerializer(serializers.ModelSerializer):
|
|
entries = TaskTemplateEntrySerializer(many=True, read_only=True)
|
|
|
|
class Meta:
|
|
model = TaskTemplate
|
|
fields = ['id', 'code', 'title', 'description', 'status', 'entries']
|
|
read_only_fields = ['id']
|
|
|
|
|
|
class TaskSerializer(serializers.ModelSerializer):
|
|
assigned_to_name = serializers.SerializerMethodField()
|
|
work_order_label = serializers.SerializerMethodField()
|
|
vehicle_registration = serializers.SerializerMethodField()
|
|
vehicle_make = serializers.SerializerMethodField()
|
|
vehicle_model = serializers.SerializerMethodField()
|
|
vehicle_owner_name = serializers.SerializerMethodField()
|
|
vehicle_asset_type = serializers.SerializerMethodField()
|
|
crane_serial_number = serializers.SerializerMethodField()
|
|
template_id = serializers.UUIDField(write_only=True, required=False, allow_null=True)
|
|
auto_close_work_order = serializers.BooleanField(write_only=True, required=False)
|
|
|
|
class Meta:
|
|
model = Task
|
|
fields = [
|
|
'id', 'title', 'description', 'status',
|
|
'assigned_to', 'assigned_to_name',
|
|
'vehicle', 'vehicle_registration', 'vehicle_make', 'vehicle_model',
|
|
'vehicle_owner_name', 'vehicle_asset_type', 'crane_serial_number',
|
|
'work_order', 'work_order_label',
|
|
'scheduled_date',
|
|
'template_id', 'auto_close_work_order',
|
|
'created_at',
|
|
]
|
|
read_only_fields = ['id', 'created_at']
|
|
extra_kwargs = {
|
|
'title': {'required': False, 'allow_blank': True},
|
|
'description': {'required': False, 'allow_blank': True},
|
|
'status': {'required': False},
|
|
'work_order': {'required': False, 'allow_null': True},
|
|
'vehicle': {'required': False, 'allow_null': True},
|
|
'scheduled_date': {'required': False, 'allow_null': True},
|
|
}
|
|
|
|
def validate_title(self, value):
|
|
if not value:
|
|
return value
|
|
if len(value) < 3:
|
|
raise serializers.ValidationError("Naslov zadatka mora imati barem 3 znaka.")
|
|
return value
|
|
|
|
def validate(self, attrs):
|
|
current = getattr(self, 'instance', None)
|
|
template_id = attrs.get('template_id')
|
|
status_value = attrs.get('status', getattr(current, 'status', 'aktivan'))
|
|
work_order = attrs.get('work_order', getattr(current, 'work_order', None))
|
|
vehicle = attrs.get('vehicle', getattr(current, 'vehicle', None))
|
|
title = (attrs.get('title', getattr(current, 'title', '')) or '').strip()
|
|
|
|
if template_id:
|
|
template = TaskTemplate.objects.filter(id=template_id, is_active=True).first()
|
|
if not template:
|
|
raise serializers.ValidationError({'template_id': 'Odabrani template ne postoji ili nije aktivan.'})
|
|
|
|
if current is None and not vehicle:
|
|
raise serializers.ValidationError({
|
|
'vehicle': "Dizalica je obavezna pri kreiranju taska."
|
|
})
|
|
|
|
if current is None and not template_id and not title:
|
|
raise serializers.ValidationError({
|
|
'title': "Naslov zadatka je obavezan ako se task ne kreira iz templatea."
|
|
})
|
|
|
|
if work_order and not vehicle:
|
|
vehicle = work_order.vehicle
|
|
attrs['vehicle'] = vehicle
|
|
if work_order and vehicle and work_order.vehicle_id != vehicle.id:
|
|
raise serializers.ValidationError({
|
|
'work_order': "Putni nalog mora pripadati istoj dizalici kao task."
|
|
})
|
|
|
|
if status_value in {'neaktivan', 'zavrsen'} and not work_order:
|
|
raise serializers.ValidationError({
|
|
'work_order': "Putni nalog je obavezan prije zatvaranja zadatka."
|
|
})
|
|
return attrs
|
|
|
|
def get_work_order_label(self, obj):
|
|
if not obj.work_order_id:
|
|
return None
|
|
display_code = str(getattr(obj.work_order, 'display_code', '') or '').strip().upper()
|
|
if display_code:
|
|
return display_code
|
|
return None
|
|
|
|
def get_assigned_to_name(self, obj):
|
|
if not obj.assigned_to_id:
|
|
return None
|
|
full_name = obj.assigned_to.get_full_name()
|
|
if full_name:
|
|
return full_name
|
|
email = getattr(obj.assigned_to, 'email', None)
|
|
if email:
|
|
return email
|
|
username = getattr(obj.assigned_to, 'username', None)
|
|
if username:
|
|
return username
|
|
return str(obj.assigned_to_id)
|
|
|
|
def get_vehicle_registration(self, obj):
|
|
if not obj.vehicle_id:
|
|
return None
|
|
return obj.vehicle.registration_number
|
|
|
|
def get_vehicle_make(self, obj):
|
|
if not obj.vehicle_id:
|
|
return None
|
|
return obj.vehicle.make or None
|
|
|
|
def get_vehicle_model(self, obj):
|
|
if not obj.vehicle_id:
|
|
return None
|
|
return obj.vehicle.model or None
|
|
|
|
def get_vehicle_owner_name(self, obj):
|
|
if not obj.vehicle_id or not obj.vehicle.client_id:
|
|
return None
|
|
return obj.vehicle.client.name
|
|
|
|
def get_vehicle_asset_type(self, obj):
|
|
if not obj.vehicle_id:
|
|
return None
|
|
return obj.vehicle.asset_type
|
|
|
|
def get_crane_serial_number(self, obj):
|
|
if not obj.vehicle_id:
|
|
return None
|
|
return obj.vehicle.crane_serial_number or None
|