v004
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user