Files
004.erp.generic/backend/modules/fleet/admin.py
mariomitte d35eaeccf4
Some checks failed
ERP CI/CD Pipeline / test (push) Has been cancelled
ERP CI/CD Pipeline / Deploy (server git pull + compose) (push) Has been cancelled
feat: dodaj ishodište i PDF račune za putni nalog
Dodano je origin_location polje kroz backend model, serializer, admin i UI
modal za kreiranje/uređivanje putnog naloga (default: Zagreb).

Računi putnog naloga sada prihvaćaju slike i PDF datoteke, uz backend
validaciju formata, podršku za serving PDF-a i prilagodbu preview prikaza.

Za greške uploada računa dodan je error toast kako bi poruke validacije bile
vidljive korisniku odmah u sučelju.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-19 19:54:15 +02:00

228 lines
8.3 KiB
Python

from django.contrib import admin
from django.contrib.admin import ModelAdmin
from django.utils.html import format_html
from .models import (
Crane,
Vehicle,
WorkOrder,
WorkOrderPhoto,
WorkOrderInvoice,
GeneratedWorkOrderPdf,
VehicleServiceRecord,
VehicleServicePhoto,
VehicleServiceAttachment,
)
class ServicePhotoInline(admin.TabularInline):
model = VehicleServicePhoto
extra = 0
fields = ('thumbnail_preview', 'image', 'description', 'uploaded_by', 'created_at')
readonly_fields = ('thumbnail_preview', 'created_at')
show_change_link = True
@admin.display(description="Pregled")
def thumbnail_preview(self, obj):
if obj.image:
return format_html(
'<a href="{}" target="_blank">'
'<img src="{}" style="height:64px;width:auto;border-radius:4px;object-fit:cover;" />'
'</a>',
obj.image.url,
obj.image.url,
)
return ""
class ServiceAttachmentInline(admin.TabularInline):
model = VehicleServiceAttachment
extra = 0
fields = ('file', 'description', 'uploaded_by', 'created_at')
readonly_fields = ('created_at',)
show_change_link = True
class WorkOrderInline(admin.TabularInline):
model = WorkOrder
extra = 0
fields = ('display_code', 'creator', 'date', 'start_mileage', 'end_mileage', 'origin_location', 'location', 'travel_start_at', 'travel_end_at', 'purpose', 'status')
readonly_fields = ('date',)
class ServiceRecordInline(admin.TabularInline):
model = VehicleServiceRecord
extra = 0
fields = ('task', 'performed_by', 'service_date', 'mileage', 'next_service_due_at', 'cost')
readonly_fields = ('service_date', 'next_service_due_at')
class WorkOrderInvoiceInline(admin.TabularInline):
model = WorkOrderInvoice
extra = 0
fields = ('naziv_racuna', 'lokacija', 'datum', 'opis', 'image', 'created_by', 'created_at', 'is_active')
readonly_fields = ('created_at',)
class WorkOrderPhotoInline(admin.TabularInline):
model = WorkOrderPhoto
extra = 0
fields = ('thumbnail_preview', 'image', 'description', 'uploaded_by', 'created_at', 'is_active')
readonly_fields = ('thumbnail_preview', 'created_at')
show_change_link = True
@admin.display(description="Pregled")
def thumbnail_preview(self, obj):
if obj.image:
return format_html(
'<a href="{}" target="_blank">'
'<img src="{}" style="height:64px;width:auto;border-radius:4px;object-fit:cover;" />'
'</a>',
obj.image.url,
obj.image.url,
)
return ""
@admin.register(Vehicle)
class VehicleAdmin(admin.ModelAdmin):
list_display = (
'registration_number',
'crane_serial_number',
'superstructure_working_hours',
'chassis_working_hours',
'make',
'model',
'year',
'current_mileage',
'service_interval_km',
'next_service_at_display',
'is_company_vehicle',
'client',
'assigned_servicer',
'is_active',
)
list_filter = ('is_active', 'is_company_vehicle', 'make', 'service_interval_km')
search_fields = ('registration_number', 'vin', 'make', 'model', 'assigned_servicer__username', 'client__name')
readonly_fields = ()
inlines = (WorkOrderInline, ServiceRecordInline)
ordering = ('registration_number',)
fieldsets = (
(None, {
'fields': ('asset_type', 'registration_number', 'vin', 'make', 'model', 'year'),
}),
('Vlasništvo', {
'fields': ('is_company_vehicle', 'client', 'assigned_servicer'),
}),
('Servis', {
'fields': ('current_mileage', 'service_interval_km', 'superstructure_working_hours', 'chassis_working_hours', 'is_active'),
}),
)
def next_service_at_display(self, obj):
try:
return obj.next_service_at()
except Exception:
return None
next_service_at_display.short_description = "Sljedeći servis @ (km)"
next_service_at_display.admin_order_field = 'current_mileage'
def get_queryset(self, request):
queryset = super().get_queryset(request)
return queryset.filter(asset_type='vehicle')
def save_model(self, request, obj, form, change):
obj.asset_type = 'vehicle'
super().save_model(request, obj, form, change)
@admin.register(Crane)
class CraneAdmin(VehicleAdmin):
list_display = (
'registration_number',
'crane_serial_number',
'superstructure_working_hours',
'chassis_working_hours',
'make',
'model',
'year',
'current_mileage',
'service_interval_km',
'next_service_at_display',
'client',
'assigned_servicer',
'is_active',
)
list_filter = ('is_active', 'client', 'make', 'service_interval_km', 'crane_serial_number')
search_fields = ('registration_number', 'crane_serial_number', 'vin', 'make', 'model', 'assigned_servicer__username', 'client__name')
fieldsets = (
(None, {
'fields': ('asset_type', 'registration_number', 'crane_serial_number', 'vin', 'make', 'model', 'year'),
}),
('Povezivanje', {
'fields': ('client', 'assigned_servicer', 'is_company_vehicle'),
}),
('Servis', {
'fields': ('current_mileage', 'service_interval_km', 'superstructure_working_hours', 'chassis_working_hours', 'is_active'),
}),
)
def get_queryset(self, request):
queryset = ModelAdmin.get_queryset(self, request)
return queryset.filter(asset_type='crane')
def save_model(self, request, obj, form, change):
obj.asset_type = 'crane'
ModelAdmin.save_model(self, request, obj, form, change)
@admin.register(WorkOrder)
class WorkOrderAdmin(admin.ModelAdmin):
list_display = ('display_code', 'id', 'vehicle', 'creator', 'date', 'origin_location', 'location', 'travel_start_at', 'travel_end_at', 'start_mileage', 'end_mileage', 'distance_display', 'status')
list_filter = ('status', 'date', 'vehicle')
search_fields = ('display_code', 'vehicle__registration_number', 'creator__username', 'purpose')
readonly_fields = ('date',)
ordering = ('-date',)
inlines = (WorkOrderInvoiceInline, WorkOrderPhotoInline)
def distance_display(self, obj):
return obj.distance
distance_display.short_description = "Prijeđeno (km)"
@admin.register(WorkOrderInvoice)
class WorkOrderInvoiceAdmin(admin.ModelAdmin):
list_display = ('id', 'work_order', 'naziv_racuna', 'lokacija', 'datum', 'created_by', 'is_active')
list_filter = ('datum', 'is_active', 'work_order')
search_fields = ('naziv_racuna', 'lokacija', 'opis', 'work_order__id')
ordering = ('-datum', '-created_at')
@admin.register(GeneratedWorkOrderPdf)
class GeneratedWorkOrderPdfAdmin(admin.ModelAdmin):
list_display = ('id', 'work_order', 'pdf_type', 'status', 'requested_by', 'expires_at', 'is_active', 'created_at')
list_filter = ('pdf_type', 'status', 'is_active', 'expires_at')
search_fields = ('work_order__id', 'requested_by__username', 'requested_by__email', 'filename')
readonly_fields = ('created_at', 'updated_at')
ordering = ('-created_at',)
@admin.register(VehicleServiceRecord)
class VehicleServiceRecordAdmin(admin.ModelAdmin):
list_display = ('id', 'service_title', 'task', 'vehicle', 'performed_by', 'service_date', 'mileage', 'next_service_due_at', 'cost', 'short_description')
list_filter = ('service_date', 'performed_by', 'vehicle')
search_fields = ('service_title', 'vehicle__registration_number', 'performed_by__username', 'description', 'parts')
readonly_fields = ('id', 'service_date', 'next_service_due_at')
ordering = ('-service_date',)
inlines = (ServicePhotoInline, ServiceAttachmentInline)
fieldsets = (
(None, {
'fields': ('id', 'service_title', 'task', 'vehicle', 'performed_by', 'service_date'),
}),
('Detalji servisa', {
'fields': ('description', 'parts', 'mileage', 'cost', 'next_service_due_at'),
}),
)
@admin.display(description="Opis")
def short_description(self, obj):
if obj.description:
return obj.description[:60] + ('' if len(obj.description) > 60 else '')
return ""