add from v002

This commit is contained in:
2026-05-20 21:24:00 +02:00
parent 1a6ba86d0b
commit 83fb8c69bb
98 changed files with 10878 additions and 0 deletions

View File

View File

@@ -0,0 +1,81 @@
from django.contrib import admin
from django.utils.html import format_html
from .models import Dogadaj
@admin.register(Dogadaj)
class DogadajAdmin(admin.ModelAdmin):
# 1. Postavke liste (Dashboard pregled)
list_display = ('prikaz_naslova', 'tip_ikona', 'pocetak', 'kraj', 'get_izvrsitelj', 'link_na_rn')
list_filter = ('tip', 'pocetak', 'serviser_u_kalendaru')
search_fields = ('naslov', 'opis', 'radni_nalog__broj_naloga', 'klijent__naziv')
ordering = ('-pocetak',)
# 2. Optimizacija baze (Spriječava N+1 problem zbog @property-ja)
def get_queryset(self, request):
return super().get_queryset(request).select_related(
'radni_nalog__izvrsitelj',
'serviser_u_kalendaru',
'klijent',
'vozilo'
)
# --- CUSTOM METODE ZA PRIKAZ ---
def prikaz_naslova(self, obj):
"""Bojanje naslova prema tipu za brzu vizualnu orijentaciju"""
boje = {
'servis': '#2980b9', # Plava
'isporuka': '#27ae60', # Zelena
'sastanak': '#f39c12', # Narančasta
'biljeska': '#7f8c8d', # Siva
}
boja = boje.get(obj.tip, 'black')
return format_html('<span style="color: {}; font-weight: bold;">{}</span>', boja, obj.naslov)
prikaz_naslova.short_description = "Naslov"
def tip_ikona(self, obj):
"""Dodaje malu ikonu uz tip događaja"""
ikone = {
'servis': '🛠️',
'isporuka': '🏗️',
'sastanak': '🤝',
'biljeska': '📝',
}
return format_html('{} {}', ikone.get(obj.tip, '📅'), obj.get_tip_display())
tip_ikona.short_description = "Tip"
def get_izvrsitelj(self, obj):
"""Prikazuje izvršitelja koristeći logiku iz model @property"""
korisnik = obj.izvrsitelj
if korisnik:
return korisnik.get_full_name() or korisnik.username
return format_html('<i style="color: #999;">Nije dodijeljeno</i>')
get_izvrsitelj.short_description = "Izvršitelj (Serviser)"
def link_na_rn(self, obj):
"""Prikazuje broj radnog naloga ako postoji"""
if obj.radni_nalog:
return format_html('<b>#{}</b>', obj.radni_nalog.broj_naloga)
return "-"
link_na_rn.short_description = "Radni Nalog"
# 3. Organizacija polja u formi (Fieldsets)
fieldsets = (
('Glavne informacije', {
'fields': (('naslov', 'tip'), 'opis')
}),
('Vrijeme održavanja', {
'fields': (('pocetak', 'kraj'),)
}),
('Poveznice sustava', {
'fields': ('radni_nalog', 'klijent', 'vozilo'),
'description': "Povežite događaj s klijentom ili radnim nalogom za bolju evidenciju."
}),
('Odgovornost', {
'fields': ('serviser_u_kalendaru',),
'description': "NAPOMENA: Ako je povezan Radni Nalog, sustav ignorira ovo polje i koristi servisera s naloga."
}),
)
# Postavljanje radnog naloga kao readonly ako želiš spriječiti ručno mijenjanje veza
# readonly_fields = ('radni_nalog',)

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class KalendarConfig(AppConfig):
name = 'kalendar'

View File

@@ -0,0 +1,61 @@
from django.db import models
from django.conf import settings
class Dogadaj(models.Model):
TIP_DOGADAJA = [
('servis', 'Radni Nalog / Servis'),
('isporuka', 'Isporuka Stroja'),
('sastanak', 'Sastanak s kupcem'),
('biljeska', 'Interna bilješka'),
]
naslov = models.CharField(max_length=200)
opis = models.TextField(blank=True)
tip = models.CharField(max_length=20, choices=TIP_DOGADAJA, default='biljeska')
pocetak = models.DateTimeField()
kraj = models.DateTimeField()
# Automatsko praćenje izmjena samog događaja u kalendaru
datum_kreiranja = models.DateTimeField(auto_now_add=True)
datum_azuriranja = models.DateTimeField(auto_now=True)
# Veze
klijent = models.ForeignKey('kupci.Kupac', on_delete=models.SET_NULL, null=True, blank=True)
vozilo = models.ForeignKey('fleet.Vozilo', on_delete=models.SET_NULL, null=True, blank=True)
radni_nalog = models.OneToOneField(
'operations.RadniNalog',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='kalendar_termin'
)
# Serviser koji je dodijeljen direktno u kalendaru (za sastanke/bilješke)
# Ako postoji radni_nalog, koristit ćemo izvrsitelja s naloga
serviser_u_kalendaru = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='dodatni_zadaci'
)
class Meta:
verbose_name = "Kalendar"
verbose_name_plural = "Kalendar"
ordering = ['pocetak']
@property
def izvrsitelj(self):
"""
Pametna metoda koja vraća servisera:
1. Ako je termin vezan za Radni Nalog, uzmi njegovog izvršitelja.
2. Ako nije, uzmi servisera upisanog u kalendar.
"""
if self.radni_nalog:
return self.radni_nalog.izvrsitelj
return self.serviser_u_kalendaru
def __str__(self):
return f"{self.naslov} - {self.izvrsitelj}"

View File

@@ -0,0 +1,46 @@
from rest_framework import serializers
from .models import Dogadaj
from operations.serializers import RadniNalogListaSerializer
class DogadajSerializer(serializers.ModelSerializer):
# 1. Mapiranje za standardne kalendarske knjižnice (FullCalendar traži title, start, end)
title = serializers.CharField(source='naslov')
start = serializers.DateTimeField(source='pocetak')
end = serializers.DateTimeField(source='kraj')
# 2. Dinamički podaci (korištenje @property-ja i Choice polja)
izvrsitelj_ime = serializers.SerializerMethodField()
tip_display = serializers.CharField(source='get_tip_display', read_only=True)
# 3. Dodatne informacije za UI
boja = serializers.SerializerMethodField()
je_radni_nalog = serializers.SerializerMethodField()
class Meta:
model = Dogadaj
fields = [
'id', 'title', 'start', 'end', 'tip', 'tip_display',
'opis', 'izvrsitelj_ime', 'boja', 'je_radni_nalog',
'klijent', 'vozilo', 'radni_nalog'
]
def get_izvrsitelj_ime(self, obj):
"""Dohvaća ime preko tvog @property izvrsitelj u modelu"""
korisnik = obj.izvrsitelj # Poziva @property iz models.py
if korisnik:
return korisnik.get_full_name() or korisnik.username
return "Nije dodijeljeno"
def get_boja(self, obj):
"""Dodjeljuje boju ovisno o tipu događaja za vizualni kalendar"""
boje = {
'servis': '#3498db', # Plava
'isporuka': '#2ecc71', # Zelena
'sastanak': '#f1c40f', # Žuta
'biljeska': '#95a5a6', # Siva
}
return boje.get(obj.tip, '#34495e')
def get_je_radni_nalog(self, obj):
"""Pomaže frontendu da zna treba li prikazati link na radni nalog"""
return obj.radni_nalog is not None

View File

@@ -0,0 +1,52 @@
from django.utils import timezone
from datetime import timedelta
from .models import Dogadaj
def kreiraj_kalendarski_unos(radni_nalog):
"""
Kreira termin u kalendaru na temelju novog radnog naloga.
"""
pocetak = timezone.now()
kraj = pocetak + timedelta(hours=2)
# DOHVAĆANJE VOZILA: Budući da vozilo više nije na Radnom nalogu,
# moramo ga izvući iz povezanog Putnog naloga (ako postoji).
vozilo_obj = None
vozilo_str = "Nije definirano"
if radni_nalog.putni_nalog and radni_nalog.putni_nalog.vozilo:
vozilo_obj = radni_nalog.putni_nalog.vozilo
vozilo_str = str(vozilo_obj)
# Kreiramo Dogadaj
dogadaj = Dogadaj.objects.create(
naslov=f"RN: {radni_nalog.broj_naloga} | {radni_nalog.klijent.naziv}",
opis=f"Vozilo: {vozilo_str}\nOpis kvara: {radni_nalog.opis_kvara}",
tip='servis',
pocetak=pocetak,
kraj=kraj,
klijent=radni_nalog.klijent,
# Ovdje spremamo instancu vozila u polje modela Dogadaj
vozilo=vozilo_obj,
radni_nalog=radni_nalog,
serviser_u_kalendaru=radni_nalog.izvrsitelj
)
return dogadaj
def azuriraj_kalendarski_unos(radni_nalog):
"""
Sinkronizira podatke u kalendaru koristeći metodu modela.
"""
try:
# Pokušaj pronaći postojeći događaj preko related_name 'kalendar_termin'
dogadaj = radni_nalog.kalendar_termin
dogadaj.sync_with_radni_nalog()
except Exception:
# Ako ne postoji (npr. nalog je stariji od uvođenja kalendara), kreiraj ga
from .services import kreiraj_kalendarski_unos
kreiraj_kalendarski_unos(radni_nalog)
def obrisi_kalendarski_unos(radni_nalog):
if hasattr(radni_nalog, 'kalendar_termin'):
radni_nalog.kalendar_termin.delete()

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,17 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import DogadajViewSet, MojRasporedView
# Kreiramo router i registriramo naš ViewSet za kalendarske događaje
router = DefaultRouter()
router.register(r'dogadaji', DogadajViewSet, basename='dogadaji')
# Definiramo URL obrasce
urlpatterns = [
# Sve CRUD rute koje router generira (npr. /kalendar/dogadaji/)
path('', include(router.urls)),
# Specijalna ruta za "današnji raspored" servisera
# Dostupna na: /kalendar/moj-raspored/
path('moj-raspored/', MojRasporedView.as_view(), name='moj-raspored'),
]

View File

@@ -0,0 +1,55 @@
from rest_framework import viewsets, generics, filters
from django_filters.rest_framework import DjangoFilterBackend
from django.utils import timezone
from django.db.models import Q
from .models import Dogadaj
from .serializers import DogadajSerializer
class DogadajViewSet(viewsets.ModelViewSet):
"""
Glavni API za kalendar. Podržava pregled svih termina,
drag-and-drop (patch) i kreiranje novih bilješki.
"""
# VAŽNO: select_related('radni_nalog__izvrsitelj') je ključan
# jer tvoj model koristi @property izvrsitelj.
# Bez ovoga, svaki termin u kalendaru bi radio poseban upit u bazu.
queryset = Dogadaj.objects.all().select_related(
'radni_nalog__izvrsitelj',
'radni_nalog__klijent',
'klijent',
'vozilo',
'serviser_u_kalendaru'
)
serializer_class = DogadajSerializer
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ['tip', 'klijent', 'vozilo']
ordering = ['pocetak']
def get_queryset(self):
"""
Opcionalno: Ako želiš da serviseri vide samo svoje termine u kalendaru,
otkomentiraj donji dio. Ako dispečer vidi sve, ostavi kako jest.
"""
qs = super().get_queryset()
# if not self.request.user.is_staff:
# return qs.filter(Q(radni_nalog__izvrsitelj=self.request.user) | Q(serviser_u_kalendaru=self.request.user))
return qs
class MojRasporedView(generics.ListAPIView):
"""
Endpoint namijenjen mobilnoj aplikaciji: "Moji zadaci za danas".
Pristup: GET /kalendar/api/moj-raspored/
"""
serializer_class = DogadajSerializer
def get_queryset(self):
user = self.request.user
danas = timezone.now().date()
# Filtriramo samo ono što je dodijeljeno ulogiranom korisniku za danas
return Dogadaj.objects.filter(
Q(pocetak__date=danas) | Q(kraj__date=danas)
).filter(
Q(radni_nalog__izvrsitelj=user) | Q(serviser_u_kalendaru=user)
).select_related('radni_nalog', 'klijent', 'vozilo')