feat: dodaj display_code polje za putne naloge (MT070626 format)
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

- Dodano polje 'display_code' na WorkOrder model (format: 2 inicijala + ddmmyy)
- Migracije za kreiranje polja i backfill postojećih naloga
- Ažuriran WorkOrderSerializer za validaciju i serijalizaciju display_code
- Backend tasks za auto-generiranje display_code pri kreiranju
- Frontend komponente ažurirane za prikaz display_code umjesto UUID:
  * FleetDashboardShell tablica putnih naloga
  * WorkOrderDetailModal
  * TaskCreateModal
  * Sve PDF stranice
- displayIds.js utility za formatiranjeDisplay ID-eva
- Admin interface prikazuje display_code
This commit is contained in:
mariomitte
2026-07-17 23:53:47 +02:00
parent ef8718a4cf
commit bf8e25efe7
19 changed files with 377 additions and 54 deletions

View File

@@ -142,17 +142,26 @@ def _parse_format(value):
GENERATED_PDF_TTL_HOURS = 24
def _work_order_display_code(work_order):
normalized = str(getattr(work_order, 'display_code', '') or '').strip().upper()
if normalized:
return normalized
return 'NALOG'
def _pdf_filename(work_order, pdf_type):
display_code = _work_order_display_code(work_order)
if pdf_type == 'invoices':
return f"{work_order.pk}.work-order-invoices.pdf"
return f"{display_code}.work-order-invoices.pdf"
if pdf_type == 'service_records':
return f"{work_order.pk}.work-order-service-records.pdf"
return f"{work_order.pk}.work-order.pdf"
return f"{display_code}.work-order-service-records.pdf"
return f"{display_code}.work-order.pdf"
def _get_cached_pdf(work_order, pdf_type):
now = timezone.now()
return (
expected_filename = _pdf_filename(work_order, pdf_type)
candidates = list(
GeneratedWorkOrderPdf.objects
.filter(
is_active=True,
@@ -164,8 +173,11 @@ def _get_cached_pdf(work_order, pdf_type):
.exclude(file='')
.exclude(file__isnull=True)
.order_by('-created_at')
.first()
)
for candidate in candidates:
if str(candidate.filename or '').strip() == expected_filename:
return candidate
return None
def _cleanup_expired_generated_pdfs():
@@ -203,17 +215,18 @@ def _notify_pdf_request(*, user, work_order, doc_type, stage, generated_pdf=None
else:
doc_label = "računa putnog naloga"
display_code = _work_order_display_code(work_order)
if stage == 'requested':
title = f"Zahtjev za PDF {doc_label}"
message = f"Zaprimljen je zahtjev za generiranje PDF dokumenta za WO-{work_order.pk}."
message = f"Zaprimljen je zahtjev za generiranje PDF dokumenta za putni nalog {display_code}."
level = "info"
elif stage == 'failed':
title = f"Greška kod PDF-a ({doc_label})"
message = f"Generiranje PDF dokumenta nije uspjelo za WO-{work_order.pk}."
message = f"Generiranje PDF dokumenta nije uspjelo za putni nalog {display_code}."
level = "warning"
else:
title = f"PDF spreman ({doc_label})"
message = f"PDF dokument je uspješno generiran za WO-{work_order.pk}."
message = f"PDF dokument je uspješno generiran za putni nalog {display_code}."
level = "success"
metadata = {
@@ -337,6 +350,7 @@ def _build_work_order_pdf(work_order):
invoices = list(work_order.invoices.filter(is_active=True).order_by('-datum', '-created_at'))
now_local = timezone.localtime(timezone.now())
tz = timezone.get_current_timezone()
display_code = _work_order_display_code(work_order)
def _fmt_date(value):
if not value:
@@ -437,7 +451,7 @@ def _build_work_order_pdf(work_order):
_paragraph("N A L O G<br/>ZA SLUŽBENO PUTOVANJE", bold=True, size=11, align=1),
_paragraph(
f"Mjesto i datum:<br/><b>{place_label} &nbsp;&nbsp; {_fmt_date(now_local.date())}</b><br/>"
f"Broj naloga:<br/><b>{work_order.pk}</b>",
f"Broj naloga:<br/><b>{display_code}</b>",
size=8.8,
),
]]
@@ -686,7 +700,7 @@ def _build_work_order_service_records_pdf(work_order):
upgrade_hours_str = str(getattr(vehicle, 'superstructure_working_hours', '-') or '-')
chassis_hours_str = str(getattr(vehicle, 'chassis_working_hours', '-') or '-')
mileage_str = str(vehicle.current_mileage or '-')
nalog_str = str(work_order.pk)
nalog_str = _work_order_display_code(work_order)
generated_date_str = timezone.localtime(timezone.now()).strftime('%d.%m.%Y')
HEADER_H = 86
@@ -1064,7 +1078,7 @@ def _build_service_record_pdf(service_record):
upgrade_hours_str = str(getattr(vehicle, 'superstructure_working_hours', '-') or '-')
chassis_hours_str = str(getattr(vehicle, 'chassis_working_hours', '-') or '-')
mileage_str = str(vehicle.current_mileage or '-')
nalog_str = str(getattr(work_order, 'pk', '-') or '-')
nalog_str = _work_order_display_code(work_order) if work_order else '-'
generated_date_str = timezone.localtime(timezone.now()).strftime('%d.%m.%Y')
HEADER_H = 86
@@ -1487,7 +1501,7 @@ class WorkOrderViewSet(viewsets.ModelViewSet):
pdf_bytes = _build_work_order_pdf(work_order)
_notify_pdf_request(user=request.user, work_order=work_order, doc_type='work_order', stage='completed')
response = HttpResponse(pdf_bytes, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="{work_order.pk}.work-order.pdf"'
response['Content-Disposition'] = f'attachment; filename="{_pdf_filename(work_order, "work_order")}"'
return response
@action(detail=True, methods=['post'], url_path='pdf-request')
@@ -1508,7 +1522,7 @@ class WorkOrderViewSet(viewsets.ModelViewSet):
pdf_bytes = _build_work_order_service_records_pdf(work_order)
_notify_pdf_request(user=request.user, work_order=work_order, doc_type='service_records', stage='completed')
response = HttpResponse(pdf_bytes, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="{work_order.pk}.work-order-service-records.pdf"'
response['Content-Disposition'] = f'attachment; filename="{_pdf_filename(work_order, "service_records")}"'
return response
@action(detail=True, methods=['post'], url_path='service-records-pdf-request')
@@ -1545,7 +1559,7 @@ class WorkOrderViewSet(viewsets.ModelViewSet):
except (ValueError, TypeError):
raise DRFValidationError({"detail": "Neispravan PDF sadržaj računa."})
filename = payload.get('filename') or f"{work_order.pk}.work-order-invoices.pdf"
filename = payload.get('filename') or _pdf_filename(work_order, 'invoices')
_notify_pdf_request(user=request.user, work_order=work_order, doc_type='invoices', stage='completed')
response = HttpResponse(pdf_bytes, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="{filename}"'
@@ -1600,7 +1614,7 @@ class WorkOrderViewSet(viewsets.ModelViewSet):
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Računi putnog naloga WO-{work_order.pk}</title>
<title>Računi putnog naloga {_work_order_display_code(work_order)}</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 24px; color: #111827; background: #f8fafc; }}
.card {{ background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px; margin-bottom: 16px; }}
@@ -1614,7 +1628,7 @@ class WorkOrderViewSet(viewsets.ModelViewSet):
</head>
<body>
<div class="card">
<h1>Putni nalog WO-{work_order.pk} — Računi</h1>
<h1>Putni nalog {_work_order_display_code(work_order)} — Računi</h1>
<p class="muted">Dizalica: {escape(work_order.vehicle.registration_number)} | Datum naloga: {escape(str(work_order.date))}</p>
<div class="actions">
<a href="{escape(pdf_url)}">Preuzmi PDF putnog naloga</a>
@@ -1693,13 +1707,13 @@ class WorkOrderViewSet(viewsets.ModelViewSet):
raise DRFValidationError({"recipient": "Nije pronađena email adresa za slanje."})
pdf_bytes = _build_work_order_pdf(work_order)
subject = _first_non_empty(request.data.get('subject')) or f"Putni nalog WO-{work_order.pk}"
subject = _first_non_empty(request.data.get('subject')) or f"Putni nalog {_work_order_display_code(work_order)}"
body = _first_non_empty(request.data.get('message')) or "U prilogu je PDF putnog naloga."
_send_document_email(
recipient=recipient,
subject=subject,
body=body,
filename=f"{work_order.pk}.work-order.pdf",
filename=_pdf_filename(work_order, 'work_order'),
pdf_bytes=pdf_bytes,
)
return Response({"sent": True, "recipient": recipient}, status=status.HTTP_200_OK)