This commit is contained in:
2026-05-22 06:08:51 +00:00
parent c078a257e9
commit bafec754d1
131 changed files with 2346 additions and 893 deletions

View File

@@ -4,7 +4,8 @@ from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from django.utils import timezone
from django.core.files import File
from operations.models import RadniNalog, RadniNalogSlika, PutniNalog
from django.db import transaction
from operations.models import RadniNalog, RadniNalogSlika, PutniNalog, BrojacSekvence
from fleet.models import Vozilo, Stroj
from kupci.models import Kupac
from kalendar.services import kreiraj_kalendarski_unos
@@ -12,10 +13,10 @@ from kalendar.services import kreiraj_kalendarski_unos
User = get_user_model()
class Command(BaseCommand):
help = 'Popunjava bazu s Putnim nalozima, Radnim nalozima i slikama'
help = 'Popunjava bazu s Putnim nalozima, Radnim nalozima i slikama sinkronizirano sa sekvencama'
def add_arguments(self, parser):
parser.add_argument('total_putovanja', type=int, help='Broj putnih naloga', default=3)
parser.add_argument('total_putovanja', type=int, nargs='?', default=3, help='Broj putnih naloga')
parser.add_argument('--img', type=str, default='test_assets/kvar.jpg')
def handle(self, *args, **kwargs):
@@ -43,23 +44,45 @@ class Command(BaseCommand):
"Hitna intervencija: Stroj u kvaru."
]
self.stdout.write(f'🚀 Generiranje {total_putovanja} putovanja...')
self.stdout.write(f'🚀 Generiranje {total_putovanja} putovanja sinkronizirano sa sekvencama...')
success_count_rn = 0
trenutna_godina = timezone.now().year
# Definiramo ključ i prefikse za putne naloge
prefix_pn = f"PN-{trenutna_godina}-"
kljuc_sekvence_rn = f"radni_nalog_{trenutna_godina}"
for i in range(total_putovanja):
serviser = random.choice(serviseri)
vozilo = random.choice(vozila)
broj_pn = f"PN-{trenutna_godina}-{PutniNalog.objects.count() + 1:04d}"
# 1. Kreiraj PUTNI NALOG (u statusu OTVOREN)
putni_nalog = PutniNalog.objects.create(
broj_naloga=broj_pn,
vozilo=vozilo,
korisnik=serviser,
relacija="Zagreb - Teren - Zagreb",
mjesto_odredista="Teren",
)
# 1. GENERIRANJE NEUNIŠTIVOG BROJA PUTNOG NALOGA
# Da skripta ne bi udarila u unique=True, tražimo stvarni zadnji PN u bazi
with transaction.atomic():
zadnji_pn = PutniNalog.objects.select_for_update().filter(
broj_naloga__startswith=prefix_pn
).order_by('-broj_naloga').first()
if zadnji_pn:
try:
zadnji_broj_pn = int(zadnji_pn.broj_naloga.split('-')[-1])
novi_broj_pn = zadnji_broj_pn + 1
except (ValueError, IndexError):
novi_broj_pn = PutniNalog.objects.count() + 1
else:
novi_broj_pn = 1
broj_pn = f"{prefix_pn}{novi_broj_pn:04d}"
# Kreiraj PUTNI NALOG
putni_nalog = PutniNalog.objects.create(
broj_naloga=broj_pn,
vozilo=vozilo,
korisnik=serviser,
relacija="Zagreb - Teren - Zagreb",
mjesto_odredista="Teren",
vrijeme_polaska=timezone.now()
)
self.stdout.write(self.style.MIGRATE_LABEL(f"Otvoren {broj_pn} (Vozilo: {vozilo.registracija})"))
@@ -69,14 +92,15 @@ class Command(BaseCommand):
for j in range(za_odraditi):
klijenti_sa_strojima = [k for k in kupci if k.strojevi.exists()]
if not klijenti_sa_strojima:
continue
klijent = random.choice(klijenti_sa_strojima)
stroj = random.choice(list(klijent.strojevi.all()))
broj_rn = f"{trenutna_godina}-{RadniNalog.objects.count() + 1:04d}"
try:
# Inicijaliziramo objekt bez broja_naloga kako bi se aktivirala
# naša neuništiva sekvencijska logika unutar save() metode modela!
nalog = RadniNalog(
broj_naloga=broj_rn,
klijent=klijent,
stroj=stroj,
putni_nalog=putni_nalog,
@@ -85,11 +109,14 @@ class Command(BaseCommand):
status=RadniNalog.StatusRadaChoices.PLANIRANO
)
# Prvo moramo izvršiti osnovni save da model kroz BrojacSekvence dodijeli čisti broj
nalog.save()
# Ako slika postoji, dodajemo je NAKON što je broj kreiran (da putanja_slike_naloga ima ispravan folder)
if test_img_path:
with open(test_img_path, 'rb') as f:
nalog.slika_kvara.save(f'rn_{broj_rn}.jpg', File(f), save=False)
nalog.slika_kvara.save(f'rn_{nalog.broj_naloga}.jpg', File(f), save=True)
nalog.save()
kreirani_radni_nalozi.append(nalog)
# Dodatne slike u galeriju
@@ -98,22 +125,24 @@ class Command(BaseCommand):
with open(test_img_path, 'rb') as f:
RadniNalogSlika.objects.create(
radni_nalog=nalog,
slika=File(f, name=f'gal_{broj_rn}_{k}.jpg'),
slika=File(f, name=f'gal_{nalog.broj_naloga}_{k}.jpg'),
opis=f"Detalj kvara {k+1}"
)
# 5. Upis u kalendar
kreiraj_kalendarski_unos(nalog)
try:
kreiraj_kalendarski_unos(nalog)
except Exception as cal_err:
self.stdout.write(self.style.WARNING(f" ⚠️ Kalendar unos preskočen: {cal_err}"))
success_count_rn += 1
self.stdout.write(f"RN-{broj_rn} dodan u {broj_pn}")
self.stdout.write(f"{nalog.broj_naloga} uspješno povezan u {broj_pn}")
except Exception as e:
self.stderr.write(f" ❌ Greška kod RN-{broj_rn}: {e}")
self.stderr.write(f" ❌ Greška kod kreiranja RN: {e}")
# 3. NASUMIČNO ZATVARANJE (Simulacija završenog posla)
# 50% šanse da ćemo u skripti odmah "završiti" ovaj put
if random.random() > 0.5:
if random.random() > 0.5 and kreirani_radni_nalozi:
try:
# Prvo moramo zatvoriti sve radne naloge da bi PN prošao validaciju
for rn in kreirani_radni_nalozi:

View File

@@ -1,4 +1,4 @@
# Generated by Django 6.0.5 on 2026-05-20 20:03
# Generated by Django 6.0.5 on 2026-05-22 03:53
import django.db.models.deletion
import django.utils.timezone
@@ -18,6 +18,18 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='BrojacSekvence',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('naziv_sekvence', models.CharField(max_length=50, unique=True)),
('zadnji_broj', models.PositiveIntegerField(default=0)),
],
options={
'verbose_name': 'Brojač sekvence',
'verbose_name_plural': 'Brojači sekvenci',
},
),
migrations.CreateModel(
name='PutniNalog',
fields=[
@@ -45,7 +57,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('broj_naloga', models.CharField(blank=True, max_length=20, unique=True)),
('opis_kvara', models.TextField()),
('status', models.CharField(choices=[('PLANIRANO', 'Planirano'), ('U_RADU', 'U radu'), ('ZAVRSENO', 'Završeno')], default='PLANIRANO', max_length=20)),
('status', models.CharField(choices=[('PLANIRANO', 'Planirano'), ('U_RADU', 'U radu'), ('ZAVRSENO', 'Završen')], default='PLANIRANO', max_length=20)),
('datum_kreiranja', models.DateTimeField(auto_now_add=True, verbose_name='Datum kreiranja')),
('datum_azuriranja', models.DateTimeField(auto_now=True, verbose_name='Zadnja izmjena')),
('slika_kvara', models.ImageField(blank=True, null=True, upload_to=operations.models.putanja_slike_naloga)),

View File

@@ -7,16 +7,31 @@ from django.utils import timezone
from django.core.files.base import ContentFile
from django.core.exceptions import ValidationError
class BrojacSekvence(models.Model):
"""
Tablica koja pamti isključivo zadnji iskorišteni broj za bilo koji entitet.
Broj se ovdje samo povećava i nikada se ne briše.
"""
naziv_sekvence = models.CharField(max_length=50, unique=True) # npr. 'radni_nalog_2026'
zadnji_broj = models.PositiveIntegerField(default=0)
class Meta:
verbose_name = "Brojač sekvence"
verbose_name_plural = "Brojači sekvenci"
def __str__(self):
return f"{self.naziv_sekvence}: {self.zadnji_broj}"
# --- FUNKCIJE ZA DINAMIČKE PUTANJE ---
def putanja_slike_naloga(instance, filename):
# Kreira putanju: radni-nalozi/broj_naloga/originalni_naziv.ekstenzija
return f'radni-nalozi/{instance.broj_naloga}/{filename}'
# Čistimo broj naloga od duplih oznaka za putanju direktorija
broj = instance.broj_naloga.replace('RN-', '') if instance.broj_naloga else 'nepoznato'
return f'radni-nalozi/RN-{broj}/{filename}'
def putanja_galerije_naloga(instance, filename):
# instance je objekt RadniNalogSlika koji ima ForeignKey na radni_nalog
# Sprema u isti folder gdje je i glavna slika kvara
return f'radni-nalozi/{instance.radni_nalog.broj_naloga}/galerija/{filename}'
broj = instance.radni_nalog.broj_naloga.replace('RN-', '') if instance.radni_nalog.broj_naloga else 'nepoznato'
return f'radni-nalozi/RN-{broj}/galerija/{filename}'
class PutniNalog(models.Model):
@@ -93,7 +108,7 @@ class RadniNalog(models.Model):
class StatusRadaChoices(models.TextChoices):
PLANIRANO = 'PLANIRANO', 'Planirano'
U_RADU = 'U_RADU', 'U radu'
ZAVRSENO = 'ZAVRSENO', 'Završeno'
ZAVRSENO = 'ZAVRSENO', 'Završen'
broj_naloga = models.CharField(max_length=20, unique=True, blank=True)
@@ -137,29 +152,41 @@ class RadniNalog(models.Model):
potpis_klijenta = models.ImageField(upload_to='potpisi/%Y/%m/', null=True, blank=True)
def save(self, *args, **kwargs):
# 1. AUTOMATSKO GENERIRANJE BROJA NALOGA
if self.broj_naloga == "":
self.broj_naloga = None
# 1. AUTOMATSKO GENERIRANJE BROJA NALOGA KROZ SEKVENCIJSKU TABLICU
if not self.broj_naloga:
godina = timezone.now().year
kljuc_sekvence = f"radni_nalog_{godina}"
prefix = f"RN-{godina}-"
# Koristimo atomsku transakciju i select_for_update da spriječimo
# da dva korisnika dobiju isti broj u istoj milisekundi
# Atomska transakcija zaključava samo brojač, sprječavajući dupliranje
with transaction.atomic():
zadnji_nalog = RadniNalog.objects.select_for_update().filter(
broj_naloga__startswith=prefix
).order_by('-broj_naloga').first()
# Dohvati ili kreiraj brojač za tekuću godinu i zaključaj redak za ažuriranje
brojac, created = BrojacSekvence.objects.select_for_update().get_or_create(
naziv_sekvence=kljuc_sekvence
)
# Ako je brojač tek kreiran, a u bazi već imaš povijest (npr. stigao si do 25),
# radimo automatsku sinkronizaciju da ne krene od 1 ako tablica nije prazna
if created:
zadnji_u_bazi = RadniNalog.objects.filter(broj_naloga__startswith=prefix).order_by('-broj_naloga').first()
if zadnji_u_bazi:
try:
brojac.zadnji_broj = int(zadnji_u_bazi.broj_naloga.split('-')[-1])
except (ValueError, IndexError):
brojac.zadnji_broj = 0
if zadnji_nalog:
try:
# RN-2026-0001 -> uzimamo "0001"
zadnji_broj_str = zadnji_nalog.broj_naloga.split('-')[-1]
novi_broj = int(zadnji_broj_str) + 1
except (ValueError, IndexError):
novi_broj = 1
else:
novi_broj = 1
# Uvećaj sekvencu za 1 (Čak i ako se nalozi obrišu, ovaj broj samo raste!)
brojac.zadnji_broj += 1
brojac.save()
self.broj_naloga = f"{prefix}{novi_broj:04d}"
# Dodijeli savršeno formatiran broj
self.broj_naloga = f"{prefix}{brojac.zadnji_broj:04d}"
else:
if self.broj_naloga.startswith("RN-RN-"):
self.broj_naloga = self.broj_naloga.replace("RN-RN-", "RN-")
# 2. OBRADA SLIKE
if self.slika_kvara:
@@ -174,9 +201,8 @@ class RadniNalog(models.Model):
# 4. LOGIKA ZA PUTNI NALOG
if self.putni_nalog and self.status == self.StatusRadaChoices.ZAVRSENO:
pn = self.putni_nalog
# Provjeravamo ima li nalog još uvijek otvorenih radnih naloga
ostali_otvoreni = pn.radni_nalozi.exclude(
id=self.id, # Isključujemo trenutni nalog jer je on upravo završen
id=self.id,
status=self.StatusRadaChoices.ZAVRSENO
).exists()
@@ -200,7 +226,10 @@ class RadniNalog(models.Model):
verbose_name_plural = "Radni nalozi"
def __str__(self):
return f"RN-{self.broj_naloga} | {self.stroj.naziv}"
# POPRAVLJENO: Budući da polje broj_naloga već u bazi sadrži "RN-2026-0004",
# ovdje izbacujemo fiksni prefiks "RN-" kako se ne bi duplirao pri pretvaranju objekta u string!
return f"{self.broj_naloga} | {self.stroj.naziv}"
class RadniNalogSlika(models.Model):
radni_nalog = models.ForeignKey(
@@ -208,7 +237,6 @@ class RadniNalogSlika(models.Model):
related_name='slike',
on_delete=models.CASCADE
)
# POPRAVLJENO: uklonjeni navodnici oko funkcije
slika = models.ImageField(upload_to=putanja_galerije_naloga, verbose_name="Slika")
opis = models.CharField(max_length=100, blank=True, verbose_name="Kratki opis slike")

View File

@@ -1,5 +1,7 @@
from django.utils import timezone # Potrebno za kreiranje vremena polaska
from django.db import transaction # Potrebno za zaključavanje baze i sprječavanje dupliranja brojeva
from rest_framework import serializers
from .models import RadniNalog, RadniNalogSlika
from .models import RadniNalog, RadniNalogSlika, PutniNalog
from kupci.serializers import KupacListaSerializer
from fleet.serializers import VoziloListaSerializer, StrojSerializer
@@ -9,6 +11,12 @@ class RadniNalogSlikaSerializer(serializers.ModelSerializer):
model = RadniNalogSlika
fields = ['id', 'slika', 'opis']
# --- POMOĆNI SERIALIZER ZA PRIKAZ VEZANOG PUTNOG NALOGA ---
class PutniNalogMinimalSerializer(serializers.ModelSerializer):
class Meta:
model = PutniNalog
fields = ['id', 'broj_naloga', 'status', 'pocetna_km']
# --- 1. LISTA SERIALIZER ---
class RadniNalogListaSerializer(serializers.ModelSerializer):
klijent_naziv = serializers.CharField(source='klijent.naziv', read_only=True)
@@ -33,6 +41,8 @@ class RadniNalogDetaljiSerializer(serializers.ModelSerializer):
stroj = StrojSerializer(read_only=True)
slike = RadniNalogSlikaSerializer(many=True, read_only=True)
putni_nalog = PutniNalogMinimalSerializer(read_only=True)
# VOZILO dohvaćamo preko putnog naloga
vozilo = serializers.SerializerMethodField()
@@ -50,19 +60,18 @@ class RadniNalogDetaljiSerializer(serializers.ModelSerializer):
read_only_fields = ['datum_kreiranja', 'datum_azuriranja']
def get_vozilo(self, obj):
# Ako je radni nalog vezan za putni nalog, vrati podatke o vozilu
if obj.putni_nalog and obj.putni_nalog.vozilo:
return {
"id": obj.putni_nalog.vozilo.id,
"naziv": obj.putni_nalog.vozilo.naziv,
"registracija": obj.putni_nalog.vozilo.registracija,
"trenutni_kilometri": obj.putni_nalog.vozilo.trenutni_kilometri
"trenutni_kilometri": obj.putni_nalog.vozilo.trenutni_kilometri,
"status_prikaz": obj.putni_nalog.vozilo.status
}
return None
# --- 3. DEFAULT SERIALIZER ---
class RadniNalogSerializer(serializers.ModelSerializer):
# Dozvoljavamo unos, ali nije obavezan ako ga backend generira
broj_naloga = serializers.CharField(required=False, read_only=True)
class Meta:
@@ -70,17 +79,81 @@ class RadniNalogSerializer(serializers.ModelSerializer):
exclude = []
def validate_broj_naloga(self, value):
# Provjera jedinstvenosti prije nego što dođe do baze
if RadniNalog.objects.filter(broj_naloga=value).exists():
raise serializers.ValidationError("Radni nalog s ovim brojem već postoji.")
return value
def validate(self, data):
# Tvoja postojeća validacija klijenta i stroja
stroj = data.get('stroj')
klijent = data.get('klijent')
if stroj and klijent and stroj.vlasnik != klijent:
raise serializers.ValidationError(
{"stroj": "Odabrani stroj ne pripada odabranom klijentu."}
)
return data
return data
# --- 4. KREIRANJE PUTNOG NALOGA SERIALIZER (AUTOMATSKO PROŠIRENJE SEKVENCI) ---
class KreirajPutniNalogSerializer(serializers.ModelSerializer):
radni_nalog_id = serializers.IntegerField(write_only=True)
class Meta:
model = PutniNalog
fields = ['id', 'radni_nalog_id', 'broj_naloga', 'vozilo', 'korisnik', 'relacija', 'mjesto_odredista', 'pocetna_km', 'status']
read_only_fields = ['id', 'broj_naloga', 'korisnik', 'relacija', 'mjesto_odredista', 'pocetna_km', 'status']
def validate_radni_nalog_id(self, value):
try:
nalog = RadniNalog.objects.get(id=value)
except RadniNalog.DoesNotExist:
raise serializers.ValidationError("Radni nalog s ovim ID-jem ne postoji.")
if nalog.putni_nalog is not None:
raise serializers.ValidationError("Za ovaj radni nalog već je izrađen putni nalog.")
if not nalog.izvrsitelj:
raise serializers.ValidationError("Radni nalog mora imati dodijeljenog izvršitelja da bi se kreirao putni nalog.")
return value
def create(self, validated_data):
radni_nalog = RadniNalog.objects.get(id=validated_data['radni_nalog_id'])
vozilo = validated_data.get('vozilo')
godina = timezone.now().year
prefix = f"PN-{godina}-"
# Koristimo atomsku transakciju i select_for_update da spriječimo trke podataka
with transaction.atomic():
# Pronalazimo zadnji kreirani putni nalog za tekuću godinu
zadnji_pn = PutniNalog.objects.select_for_update().filter(
broj_naloga__startswith=prefix
).order_by('-broj_naloga').first()
if zadnji_pn:
try:
# PN-2026-0002 -> uzimamo zadnji segment "0002" i uvećavamo ga
zadnji_broj_str = zadnji_pn.broj_naloga.split('-')[-1]
novi_broj = int(zadnji_broj_str) + 1
except (ValueError, IndexError):
novi_broj = 1
else:
novi_broj = 1
# Sklapanje jedinstvenog broja putnog naloga u formatu PN-2026-XXXX
broj_pn = f"{prefix}{novi_broj:04d}"
# Siguran upis u bazu podataka
putni_nalog = PutniNalog.objects.create(
broj_naloga=broj_pn,
vozilo=vozilo,
korisnik=radni_nalog.izvrsitelj,
relacija=f"Zagreb - {radni_nalog.klijent.grad} - Zagreb",
mjesto_odredista=radni_nalog.klijent.grad,
vrijeme_polaska=timezone.now()
)
# Povezujemo radni nalog na novokreirani putni nalog
radni_nalog.putni_nalog = putni_nalog
radni_nalog.save()
return putni_nalog

View File

@@ -1,10 +1,11 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import RadniNalogViewSet
from .views import RadniNalogViewSet, PutniNalogViewSet
# Kreiramo router i registriramo ViewSet za radne naloge
router = DefaultRouter()
router.register(r'radni-nalozi', RadniNalogViewSet, basename='radni-nalog')
router.register(r'putni-nalozi', PutniNalogViewSet, basename='putni-nalog')
urlpatterns = [
# Uključujemo sve rute koje router generira

View File

@@ -1,13 +1,14 @@
from rest_framework.decorators import action
from django.utils import timezone
from rest_framework import viewsets, filters, status # Dodan status
from rest_framework import viewsets, filters, permissions, status
from django_filters.rest_framework import DjangoFilterBackend
from .models import RadniNalog, RadniNalogSlika, PutniNalog
from rest_framework.response import Response
from .serializers import (
RadniNalogSerializer,
RadniNalogListaSerializer,
RadniNalogDetaljiSerializer
RadniNalogDetaljiSerializer,
KreirajPutniNalogSerializer
)
from kalendar.services import kreiraj_kalendarski_unos
@@ -16,7 +17,6 @@ class RadniNalogViewSet(viewsets.ModelViewSet):
API endpoint za upravljanje radnim nalozima.
Vozilo se sada filtrira i pretražuje preko Putnog Naloga.
"""
# 1. queryset: UKLONJEN 'vozilo' iz select_related
queryset = RadniNalog.objects.all().select_related(
'klijent', 'izvrsitelj', 'putni_nalog__vozilo'
)
@@ -29,10 +29,8 @@ class RadniNalogViewSet(viewsets.ModelViewSet):
filters.OrderingFilter
]
# 2. filterset_fields: 'vozilo' zamijenjeno putanjom preko putnog naloga
filterset_fields = ['status', 'izvrsitelj', 'klijent', 'putni_nalog__vozilo']
# 3. search_fields: pretraga ide preko putni_nalog__vozilo__registracija
search_fields = [
'broj_naloga',
'klijent__naziv',
@@ -51,25 +49,24 @@ class RadniNalogViewSet(viewsets.ModelViewSet):
return RadniNalogSerializer
def get_queryset(self):
# Optimizacija upita ovisno o akciji
queryset = self.queryset
if self.action == 'retrieve':
queryset = queryset.prefetch_related('slike')
return queryset
def perform_create(self, serializer):
# Postavljamo izvršitelja ako nije poslan
if not serializer.validated_data.get('izvrsitelj'):
radni_nalog = serializer.save(izvrsitelj=self.request.user)
else:
radni_nalog = serializer.save()
# Kalendar servis
try:
kreiraj_kalendarski_unos(radni_nalog)
except Exception as e:
print(f"Greška pri kreiranju kalendarskog zapisa: {e}")
# Uredi isključivo ovu metodu unutar RadniNalogViewSet u tvom views.py
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
@@ -82,25 +79,73 @@ class RadniNalogViewSet(viewsets.ModelViewSet):
for f in files:
RadniNalogSlika.objects.create(radni_nalog=instance, slika=f)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
@action(detail=False, methods=['get'], url_path='sljedeci-broj')
def dohvati_sljedeci_broj(self, request):
godina = timezone.now().year
prefix = f"{godina}-"
# KLJUČNI POPRAVAK: Ponovno osvježavamo instancu iz baze podataka
# Ovo povlači čisti 'broj_naloga' nakon što ga je save() u models.py očistio od RN-RN-
instance.refresh_from_db()
zadnji_nalog = RadniNalog.objects.filter(
broj_naloga__startswith=prefix
).order_by('-broj_naloga').first()
if zadnji_nalog:
try:
zadnji_broj = int(zadnji_nalog.broj_naloga.split('-')[-1])
novi_broj = zadnji_broj + 1
except (ValueError, IndexError):
novi_broj = 1
# Za povratni JSON koristimo detaljniji prikaz kako bi Astro odmah dobio ispravno stanje
izlazni_serializer = RadniNalogDetaljiSerializer(instance, context={'request': request})
headers = self.get_success_headers(izlazni_serializer.data)
return Response(izlazni_serializer.data, status=status.HTTP_201_CREATED, headers=headers)
@action(detail=False, methods=['get'], url_path='sljedeci-broj', permission_classes=[permissions.AllowAny])
def dohvati_sljedeci_broj(self, request):
"""
Dohvaća točan sljedeći broj radnog naloga iz neuništive sekvencijske tablice.
"""
godina = timezone.now().year
kljuc_sekvence = f"radni_nalog_{godina}"
prefix = f"RN-{godina}-"
# Pogledaj trenutačno stanje u tablici sekvenci (bez uvećavanja)
from .models import BrojacSekvence # Lokalni uvoz ako zatreba
brojac = BrojacSekvence.objects.filter(naziv_sekvence=kljuc_sekvence).first()
if brojac:
sljedeci_broj = brojac.zadnji_broj + 1
else:
novi_broj = 1
# Ako brojač još ne postoji u sekvencama, baci oko na bazu za prvu inicijalizaciju
zadnji_nalog = RadniNalog.objects.filter(broj_naloga__startswith=prefix).order_by('-broj_naloga').first()
if zadnji_nalog:
try:
sljedeci_broj = int(zadnji_nalog.broj_naloga.split('-')[-1]) + 1
except (ValueError, IndexError):
sljedeci_broj = 1
else:
sljedeci_broj = 1
sljedeci_broj_formatiran = f"{prefix}{sljedeci_broj:04d}"
return Response({"broj_naloga": sljedeci_broj_formatiran})
class PutniNalogViewSet(viewsets.ModelViewSet):
"""
ViewSet za upravljanje putnim nalozima.
Podržava izradu novog putnog naloga iz radnog naloga (POST) i pregled (GET).
"""
queryset = PutniNalog.objects.all().order_by('-datum_izdavanja', '-id')
permission_classes = [permissions.IsAuthenticated] # Preporučeno: Otkomentarisano radi getAuthHeaders
return Response({"broj_naloga": f"{prefix}{novi_broj:04d}"})
def get_serializer_class(self):
if self.action == 'create':
return KreirajPutniNalogSerializer
return KreirajPutniNalogSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer_class()(data=request.data, context={'request': request})
if serializer.is_valid():
putni_nalog = serializer.save()
return Response(
{
"id": putni_nalog.id,
"broj_naloga": putni_nalog.broj_naloga,
"status": putni_nalog.status,
"pocetna_km": putni_nalog.pocetna_km,
"message": "Putni nalog uspješno kreiran!"
},
status=status.HTTP_201_CREATED
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)