prije gemini provjere
This commit is contained in:
122
001.BACKEND/operations/services.py
Normal file
122
001.BACKEND/operations/services.py
Normal file
@@ -0,0 +1,122 @@
|
||||
# operations/services.py
|
||||
import os
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from rest_framework import serializers
|
||||
from .models import RadniNalog, PutniNalog
|
||||
from kalendar.services import kreiraj_kalendarski_unos
|
||||
|
||||
def izvrsi_perform_create_logiku(serializer, user):
|
||||
"""
|
||||
Konzistentna poslovna logika za kreiranje radnog naloga.
|
||||
Dodjeljuje izvršitelja i automatski okida kalendarski servis.
|
||||
"""
|
||||
if not serializer.validated_data.get('izvrsitelj'):
|
||||
radni_nalog = serializer.save(izvrsitelj=user)
|
||||
else:
|
||||
radni_nalog = serializer.save()
|
||||
|
||||
try:
|
||||
kreiraj_kalendarski_unos(radni_nalog)
|
||||
except Exception as e:
|
||||
# Možeš uvesti standardni logging sustav umjesto printa
|
||||
print(f"Greška pri kreiranju kalendarskog zapisa: {e}")
|
||||
|
||||
return radni_nalog
|
||||
|
||||
|
||||
def generiraj_i_kreiraj_putni_nalog(radni_nalog_id, vozilo):
|
||||
"""
|
||||
Enkapsulirana atomska logika za generiranje jedinstvenog broja sekvence
|
||||
i kreiranje putnog naloga, sprječavajući dupliciranje brojeva pod opterećenjem.
|
||||
"""
|
||||
radni_nalog = RadniNalog.objects.get(id=radni_nalog_id)
|
||||
godina = timezone.now().year
|
||||
prefix = f"PN-{godina}-"
|
||||
|
||||
with transaction.atomic():
|
||||
# Zaključavamo zapise za tekuću godinu (Pessimistic Locking)
|
||||
zadnji_pn = PutniNalog.objects.select_for_update().filter(
|
||||
broj_naloga__startswith=prefix
|
||||
).order_by('-broj_naloga').first()
|
||||
|
||||
if zadnji_pn:
|
||||
try:
|
||||
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
|
||||
|
||||
# Formatiranje broja: PN-2026-0001
|
||||
broj_pn = f"{prefix}{novi_broj:04d}"
|
||||
|
||||
# Siguran upis u bazu
|
||||
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()
|
||||
)
|
||||
|
||||
# Povezivanje s radnim nalogom
|
||||
radni_nalog.putni_nalog = putni_nalog
|
||||
radni_nalog.save()
|
||||
|
||||
return putni_nalog
|
||||
|
||||
def get_optimized_image(image_url, width=600):
|
||||
"""
|
||||
On-the-fly optimizacija slike u memoriji.
|
||||
Prima URL ili lokalnu stazu, vraća sirove bajtove WebP slike.
|
||||
"""
|
||||
# 1. Dohvaćanje slike (podržava lokalne relativne staze i apsolutne URL-ove)
|
||||
if image_url.startswith('http://') or image_url.startswith('https://'):
|
||||
odgovor = requests.get(image_url, timeout=5)
|
||||
odgovor.raise_for_status()
|
||||
img_data = BytesIO(odgovor.content)
|
||||
else:
|
||||
# Pretvaramo relativnu stazu (npr. /media/slika.jpg) u apsolutnu stazu na disku
|
||||
# Mičemo početni slash ako postoji da osiguramo ispravno spajanje staza
|
||||
cista_staza = image_url.lstrip('/')
|
||||
if cista_staza.startswith('media/'):
|
||||
cista_staza = cista_staza.replace('media/', '', 1)
|
||||
|
||||
staza_na_disku = os.path.join(settings.MEDIA_ROOT, cista_staza)
|
||||
|
||||
if not os.path.exists(staza_na_disku):
|
||||
raise FileNotFoundError(f"Slika nije pronađena na lokaciji: {staza_na_disku}")
|
||||
|
||||
with open(staza_na_disku, 'rb') as f:
|
||||
img_data = BytesIO(f.read())
|
||||
|
||||
# 2. Obrada slike kroz PIL (Pillow) paket
|
||||
with Image.open(img_data) as img:
|
||||
# Osiguranje kompatibilnosti profila boja (RGBA -> RGB za WebP)
|
||||
if img.mode in ('RGBA', 'LA'):
|
||||
pozadina = Image.new('RGB', img.size, (255, 255, 255))
|
||||
pozadina.paste(img, mask=img.split()[3])
|
||||
img = pozadina
|
||||
elif img.mode == 'P':
|
||||
img = img.convert('RGB')
|
||||
|
||||
# Proporcionalni resize ako slika prelazi željenu širinu
|
||||
if img.width > width:
|
||||
faktor_skaliranja = width / float(img.width)
|
||||
nova_visina = int(float(img.height) * float(faktor_skaliranja))
|
||||
img = img.resize((width, nova_visina), Image.Resampling.LANCZOS)
|
||||
|
||||
# 3. Spremanje u memorijski buffer umjesto na disk
|
||||
memorijski_buffer = BytesIO()
|
||||
# Smanjujemo kvalitetu na 80% (idealni omjer oštrine i težine u bajtovima)
|
||||
img.save(memorijski_buffer, format='WEBP', quality=80)
|
||||
memorijski_buffer.seek(0)
|
||||
|
||||
return memorijski_buffer.getvalue()
|
||||
Reference in New Issue
Block a user