prije gemini provjere
This commit is contained in:
248
001.BACKEND/operations/models.py
Normal file
248
001.BACKEND/operations/models.py
Normal file
@@ -0,0 +1,248 @@
|
||||
import os
|
||||
from PIL import Image
|
||||
import io
|
||||
from django.db import models, transaction
|
||||
from django.conf import settings
|
||||
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):
|
||||
# Č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):
|
||||
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):
|
||||
class StatusChoices(models.TextChoices):
|
||||
OTVOREN = 'OTVOREN', 'Otvoren'
|
||||
ZAVRSEN = 'ZAVRSEN', 'Završen'
|
||||
|
||||
broj_naloga = models.CharField(max_length=50, unique=True)
|
||||
datum_izdavanja = models.DateField(default=timezone.now)
|
||||
|
||||
vozilo = models.ForeignKey(
|
||||
'fleet.Vozilo',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='putni_nalozi',
|
||||
verbose_name="Službeno vozilo"
|
||||
)
|
||||
|
||||
korisnik = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||
relacija = models.CharField(max_length=255)
|
||||
mjesto_odredista = models.CharField(max_length=255)
|
||||
|
||||
pocetna_km = models.PositiveIntegerField(
|
||||
editable=False,
|
||||
help_text="Automatski preuzeto iz modela Vozilo"
|
||||
)
|
||||
zavrsna_km = models.PositiveIntegerField(null=True, blank=True)
|
||||
|
||||
vrijeme_polaska = models.DateTimeField(default=timezone.now)
|
||||
vrijeme_povratka = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
status = models.CharField(
|
||||
max_length=15,
|
||||
choices=StatusChoices.choices,
|
||||
default=StatusChoices.OTVOREN
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Putni nalog"
|
||||
verbose_name_plural = "Putni nalozi"
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.status == self.StatusChoices.ZAVRSEN:
|
||||
if self.zavrsna_km is None:
|
||||
raise ValidationError({'zavrsna_km': "Morate unijeti završne kilometre prije zatvaranja naloga."})
|
||||
|
||||
otvoreni_radni_nalozi = self.radni_nalozi.exclude(status='ZAVRSENO').exists()
|
||||
if otvoreni_radni_nalozi:
|
||||
raise ValidationError({
|
||||
'status': "Putni nalog ne može biti završen jer postoje povezani radni nalozi koji još nisu zatvoreni."
|
||||
})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pk:
|
||||
self.pocetna_km = self.vozilo.trenutni_kilometri
|
||||
|
||||
self.full_clean()
|
||||
|
||||
if self.status == self.StatusChoices.ZAVRSEN and self.zavrsna_km:
|
||||
with transaction.atomic():
|
||||
super().save(*args, **kwargs)
|
||||
v = self.vozilo
|
||||
v.trenutni_kilometri = self.zavrsna_km
|
||||
v.pocetni_kilometri = self.zavrsna_km
|
||||
v.save()
|
||||
else:
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.broj_naloga} - {self.vozilo.registracija}"
|
||||
|
||||
|
||||
class RadniNalog(models.Model):
|
||||
class StatusRadaChoices(models.TextChoices):
|
||||
PLANIRANO = 'PLANIRANO', 'Planirano'
|
||||
U_RADU = 'U_RADU', 'U radu'
|
||||
ZAVRSENO = 'ZAVRSENO', 'Završen'
|
||||
|
||||
broj_naloga = models.CharField(max_length=20, unique=True, blank=True)
|
||||
|
||||
stroj = models.ForeignKey(
|
||||
'fleet.Stroj',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='radni_nalozi',
|
||||
verbose_name="Stroj na popravku"
|
||||
)
|
||||
|
||||
putni_nalog = models.ForeignKey(
|
||||
'PutniNalog',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True, blank=True,
|
||||
related_name='radni_nalozi'
|
||||
)
|
||||
|
||||
klijent = models.ForeignKey(
|
||||
'kupci.Kupac',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='radni_nalozi'
|
||||
)
|
||||
|
||||
opis_kvara = models.TextField()
|
||||
izvrsitelj = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True, blank=True,
|
||||
related_name='dodijeljeni_nalozi'
|
||||
)
|
||||
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=StatusRadaChoices.choices,
|
||||
default=StatusRadaChoices.PLANIRANO
|
||||
)
|
||||
|
||||
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(upload_to=putanja_slike_naloga, null=True, blank=True)
|
||||
potpis_klijenta = models.ImageField(upload_to='potpisi/%Y/%m/', null=True, blank=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
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}-"
|
||||
|
||||
# Atomska transakcija zaključava samo brojač, sprječavajući dupliranje
|
||||
with transaction.atomic():
|
||||
# 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
|
||||
|
||||
# Uvećaj sekvencu za 1 (Čak i ako se nalozi obrišu, ovaj broj samo raste!)
|
||||
brojac.zadnji_broj += 1
|
||||
brojac.save()
|
||||
|
||||
# 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:
|
||||
try:
|
||||
self.slika_kvara = self.rescale_image(self.slika_kvara)
|
||||
except Exception as e:
|
||||
print(f"Greška pri obradi slike: {e}")
|
||||
|
||||
# 3. PRIMARNO SPREMANJE
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# 4. LOGIKA ZA PUTNI NALOG
|
||||
if self.putni_nalog and self.status == self.StatusRadaChoices.ZAVRSENO:
|
||||
pn = self.putni_nalog
|
||||
ostali_otvoreni = pn.radni_nalozi.exclude(
|
||||
id=self.id,
|
||||
status=self.StatusRadaChoices.ZAVRSENO
|
||||
).exists()
|
||||
|
||||
if not ostali_otvoreni and pn.zavrsna_km:
|
||||
pn.status = 'ZAVRSEN'
|
||||
pn.save()
|
||||
|
||||
def rescale_image(self, image_field):
|
||||
img = Image.open(image_field)
|
||||
if img.height > 1200 or img.width > 1200:
|
||||
output_size = (1200, 1200)
|
||||
img.thumbnail(output_size)
|
||||
img_io = io.BytesIO()
|
||||
img_format = img.format if img.format else 'JPEG'
|
||||
img.save(img_io, format=img_format, quality=90)
|
||||
return ContentFile(img_io.getvalue(), name=image_field.name)
|
||||
return image_field
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Radni nalog"
|
||||
verbose_name_plural = "Radni nalozi"
|
||||
|
||||
def __str__(self):
|
||||
# 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(
|
||||
RadniNalog,
|
||||
related_name='slike',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
slika = models.ImageField(upload_to=putanja_galerije_naloga, verbose_name="Slika")
|
||||
opis = models.CharField(max_length=100, blank=True, verbose_name="Kratki opis slike")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Dodatna slika naloga"
|
||||
verbose_name_plural = "Dodatne slike naloga"
|
||||
|
||||
def __str__(self):
|
||||
return f"Slika za nalog {self.radni_nalog.broj_naloga}"
|
||||
Reference in New Issue
Block a user