diff --git a/backend/modules/fleet/views.py b/backend/modules/fleet/views.py index 50a92ca..77e6881 100644 --- a/backend/modules/fleet/views.py +++ b/backend/modules/fleet/views.py @@ -900,17 +900,25 @@ def _build_work_order_service_records_pdf(work_order): FOOTER_H = 72 MARGIN = 28 + related_tasks = list(_work_order_related_tasks_queryset(work_order)) + # Collect service records for all task vehicles (supports cross-crane work orders) + task_vehicle_ids = list({task.vehicle_id for task in related_tasks if task.vehicle_id}) service_rows = list( VehicleServiceRecord.objects.filter( is_active=True, - vehicle_id=work_order.vehicle_id, + vehicle_id__in=task_vehicle_ids if task_vehicle_ids else [work_order.vehicle_id], ) .select_related('performed_by', 'task') .prefetch_related('photos') .order_by('service_date', 'created_at') ) - related_tasks = list(_work_order_related_tasks_queryset(work_order)) - notes_text = str(work_order.notes or '').strip() + # Per-task notes; fall back to work_order.notes + task_note_parts = [ + str(task.service_report_note or '').strip() + for task in related_tasks + if str(task.service_report_note or '').strip() + ] + notes_text = "\n".join(task_note_parts) if task_note_parts else str(work_order.notes or '').strip() buffer = BytesIO() @@ -1812,9 +1820,8 @@ def _build_work_order_service_records_docx_bytes(work_order): client_name = getattr(getattr(vehicle, 'client', None), 'name', None) or '-' servicer_name = _user_display_name(work_order.creator) or '-' completion_label = "Da ☒ Ne ☐" if work_order.status == 'closed' else "Da ☐ Ne ☒" - notes_text = str(work_order.notes or '').strip() or '-' - related_tasks = list(_work_order_related_tasks_queryset(work_order)) + related_tasks = list(_work_order_related_tasks_queryset(work_order).select_related('work_hours_table')) normalized_rows = [] for task in related_tasks: table_data = getattr(getattr(task, 'work_hours_table', None), 'data', None) @@ -1847,10 +1854,13 @@ def _build_work_order_service_records_docx_bytes(work_order): 'vehicle_km': str(row.get('vehicle_km', '') or '').strip() or '-', }) + # Collect service records per task, filtering by each task's own vehicle + # (supports cross-crane work orders where each task may have a different crane). + task_vehicle_ids = list({task.vehicle_id for task in related_tasks if task.vehicle_id}) service_rows = list( VehicleServiceRecord.objects.filter( is_active=True, - vehicle_id=work_order.vehicle_id, + vehicle_id__in=task_vehicle_ids, ) .select_related('performed_by', 'task') .prefetch_related('photos') @@ -1861,6 +1871,14 @@ def _build_work_order_service_records_docx_bytes(work_order): for task in related_tasks } + # Build notes: use per-task service_report_note; fall back to work_order.notes + task_note_parts = [ + str(task.service_report_note or '').strip() + for task in related_tasks + if str(task.service_report_note or '').strip() + ] + notes_text = "\n".join(task_note_parts) if task_note_parts else (str(work_order.notes or '').strip() or '-') + if len(doc.tables) >= 4: info_table = doc.tables[0] _set_docx_cell_text(info_table, 1, 0, client_name) @@ -1893,7 +1911,6 @@ def _build_work_order_service_records_docx_bytes(work_order): target_row.cells[3].text = entry['changed_at'] details_table = doc.tables[3] - issue_text = str(work_order.notes or '-').strip() or '-' repair_lines = [] for task in related_tasks: task_records = records_by_task.get(task.id, []) @@ -1904,7 +1921,7 @@ def _build_work_order_service_records_docx_bytes(work_order): description = str(record.description or '-').strip() or '-' repair_lines.append(f"- {description}") repair_text = "\n".join(repair_lines) if repair_lines else '-' - _set_docx_cell_text(details_table, 0, 0, f"Kvar: {issue_text}") + _set_docx_cell_text(details_table, 0, 0, f"Kvar: {notes_text}") _set_docx_cell_text(details_table, 1, 0, f"Popravak: {repair_text}") else: raise DRFValidationError({"detail": "DOCX template ima neočekivanu strukturu (nedostaju tablice)."}) @@ -2922,7 +2939,10 @@ class WorkOrderViewSet(viewsets.ModelViewSet): payload = [] for task in tasks: records_payload = [] - for record in task.service_records.filter(is_active=True, vehicle_id=work_order.vehicle_id).all(): + # Filter records by the task's own vehicle (not work_order.vehicle) to support + # cross-crane work orders where each task may belong to a different crane. + task_vehicle_id = task.vehicle_id + for record in task.service_records.filter(is_active=True, vehicle_id=task_vehicle_id).all(): photos_payload = [] for photo in record.photos.filter(is_active=True).all(): if not photo.image: @@ -2950,6 +2970,7 @@ class WorkOrderViewSet(viewsets.ModelViewSet): 'title': task.title, 'status': task.status, 'description': task.description, + 'service_report_note': task.service_report_note or '', 'assigned_to_name': _user_display_name(task.assigned_to), 'vehicle_registration': getattr(task.vehicle, 'registration_number', None), 'work_hours_table': getattr(getattr(task, 'work_hours_table', None), 'data', None), diff --git a/backend/modules/task_management/migrations/0012_task_service_report_note.py b/backend/modules/task_management/migrations/0012_task_service_report_note.py new file mode 100644 index 0000000..1c0a704 --- /dev/null +++ b/backend/modules/task_management/migrations/0012_task_service_report_note.py @@ -0,0 +1,21 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('task_management', '0011_taskcalendar'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='service_report_note', + field=models.TextField( + blank=True, + default='', + verbose_name='Napomena izvještaja servisera', + help_text='Tekst napomene za PDF izvještaj servisera za ovaj zadatak.', + ), + ), + ] diff --git a/backend/modules/task_management/models.py b/backend/modules/task_management/models.py index b88cae2..8a220e4 100644 --- a/backend/modules/task_management/models.py +++ b/backend/modules/task_management/models.py @@ -54,6 +54,12 @@ class Task(BaseModel): verbose_name=_("Planirani datum izvršenja"), help_text=_("Datum kada je planirano izvršenje radnog zadatka."), ) + service_report_note = models.TextField( + blank=True, + default='', + verbose_name=_("Napomena izvještaja servisera"), + help_text=_("Tekst napomene za PDF izvještaj servisera za ovaj zadatak."), + ) class Meta: ordering = ['-created_at'] diff --git a/backend/modules/task_management/serializers.py b/backend/modules/task_management/serializers.py index be87226..1fb2ba9 100644 --- a/backend/modules/task_management/serializers.py +++ b/backend/modules/task_management/serializers.py @@ -103,6 +103,7 @@ class TaskSerializer(serializers.ModelSerializer): 'vehicle_owner_name', 'vehicle_asset_type', 'crane_serial_number', 'work_order', 'work_order_label', 'scheduled_date', + 'service_report_note', 'template_id', 'auto_close_work_order', 'created_at', ] @@ -114,6 +115,7 @@ class TaskSerializer(serializers.ModelSerializer): 'work_order': {'required': False, 'allow_null': True}, 'vehicle': {'required': False, 'allow_null': True}, 'scheduled_date': {'required': False, 'allow_null': True}, + 'service_report_note': {'required': False, 'allow_blank': True}, } def validate_title(self, value): diff --git a/frontend/src/components/dashboard/TaskWorkHoursTableModal.jsx b/frontend/src/components/dashboard/TaskWorkHoursTableModal.jsx index 983656d..b0a0627 100644 --- a/frontend/src/components/dashboard/TaskWorkHoursTableModal.jsx +++ b/frontend/src/components/dashboard/TaskWorkHoursTableModal.jsx @@ -137,6 +137,7 @@ const FB_MUTED = [ export default function TaskWorkHoursTableModal({ open, task, + workOrder = null, defaultLocation = '', saving = false, onClose, @@ -337,6 +338,39 @@ export default function TaskWorkHoursTableModal({
Podaci iz putnog naloga (samo za prikaz)
+