import uuid import re from datetime import time from django.db import models from django.conf import settings from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.utils import timezone from core.base_models import BaseModel from decimal import Decimal def _service_photo_upload_to(instance, filename): """ Putanja za upload: fleet/{registration_number}/service_photos/{uuid4}_{original_filename} Koristimo uuid jer instance.service_record.pk možda nije dostupna prije prvog save-a. """ vehicle_reg = 'unknown' try: vehicle_reg = instance.service_record.vehicle.registration_number except Exception: # fallback vehicle_reg = 'unknown' unique_name = f"{uuid.uuid4().hex}_{filename}" return f"fleet/{vehicle_reg}/service_photos/{unique_name}" def _service_attachment_upload_to(instance, filename): """ Putanja za upload: fleet/{registration_number}/service_attachments/{uuid4}_{original_filename} """ vehicle_reg = 'unknown' try: vehicle_reg = instance.service_record.vehicle.registration_number except Exception: vehicle_reg = 'unknown' unique_name = f"{uuid.uuid4().hex}_{filename}" return f"fleet/{vehicle_reg}/service_attachments/{unique_name}" def _work_order_invoice_upload_to(instance, filename): vehicle_reg = 'unknown' try: vehicle_reg = instance.work_order.vehicle.registration_number except Exception: vehicle_reg = 'unknown' unique_name = f"{uuid.uuid4().hex}_{filename}" return f"fleet/{vehicle_reg}/work_order_invoices/{unique_name}" def _work_order_photo_upload_to(instance, filename): vehicle_reg = 'unknown' try: vehicle_reg = instance.work_order.vehicle.registration_number except Exception: vehicle_reg = 'unknown' unique_name = f"{uuid.uuid4().hex}_{filename}" return f"fleet/{vehicle_reg}/work_order_photos/{unique_name}" def _generated_work_order_pdf_upload_to(instance, filename): vehicle_reg = 'unknown' try: vehicle_reg = instance.work_order.vehicle.registration_number except Exception: vehicle_reg = 'unknown' unique_name = f"{uuid.uuid4().hex}_{filename}" return f"fleet/{vehicle_reg}/generated_pdfs/{unique_name}" class VehicleServicePhoto(BaseModel): """ Fotografije kvarova / radova povezane s VehicleServiceRecord. Pohranjuju se slike i opcionalni opis te tko ih je upload-ao. """ service_record = models.ForeignKey( 'fleet.VehicleServiceRecord', on_delete=models.CASCADE, related_name='photos', verbose_name=_("Servisni zapis") ) image = models.ImageField( upload_to=_service_photo_upload_to, verbose_name=_("Fotografija") ) description = models.CharField(max_length=255, blank=True, verbose_name=_("Opis")) uploaded_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='uploaded_service_photos', verbose_name=_("Postavio") ) is_active = models.BooleanField(default=True, verbose_name=_("Aktivan")) class Meta: verbose_name = _("Fotografija servisa") verbose_name_plural = _("Fotografije servisa") ordering = ['-created_at'] def __str__(self): return f"Photo {self.pk} for {self.service_record} ({self.image.name if self.image else 'no-image'})" class VehicleServiceAttachment(BaseModel): """ Datoteke (PDF, DOC, XLS...) povezane sa servisnim zapisom. """ service_record = models.ForeignKey( 'fleet.VehicleServiceRecord', on_delete=models.CASCADE, related_name='attachments', verbose_name=_("Servisni zapis") ) file = models.FileField( upload_to=_service_attachment_upload_to, verbose_name=_("Datoteka") ) description = models.CharField(max_length=255, blank=True, verbose_name=_("Opis")) uploaded_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='uploaded_service_attachments', verbose_name=_("Postavio") ) is_active = models.BooleanField(default=True, verbose_name=_("Aktivan")) class Meta: verbose_name = _("Prilog servisa") verbose_name_plural = _("Prilozi servisa") ordering = ['-created_at'] def __str__(self): return f"Attachment {self.pk} for {self.service_record} ({self.file.name if self.file else 'no-file'})" class Vehicle(BaseModel): ASSET_TYPE_CHOICES = [ ('vehicle', _("Vozilo")), ('crane', _("Dizalica")), ] asset_type = models.CharField( max_length=20, choices=ASSET_TYPE_CHOICES, default='crane', db_index=True, verbose_name=_("Tip sredstva"), ) registration_number = models.CharField(max_length=20, unique=True, verbose_name=_("Registracija")) make = models.CharField(max_length=100, blank=True, verbose_name=_("Proizvođač")) model = models.CharField(max_length=100, blank=True, verbose_name=_("Model")) year = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name=_("Godina")) vin = models.CharField(max_length=50, blank=True, null=True, unique=True, verbose_name=_("VIN")) crane_serial_number = models.CharField( max_length=6, null=True, blank=True, db_index=True, verbose_name=_("Serijski broj dizalice"), help_text=_("Serijski broj dizalice mora imati točno 6 znamenki."), ) superstructure_working_hours = models.PositiveIntegerField( default=0, verbose_name=_("Radni sati nadogradnje"), ) chassis_working_hours = models.PositiveIntegerField( default=0, verbose_name=_("Radni sati podvozja"), ) current_mileage = models.PositiveIntegerField(default=0, verbose_name=_("Trenutna kilometraža (km)")) service_interval_km = models.PositiveIntegerField(default=15000, verbose_name=_("Interval servisa (km)")) assigned_servicer = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='assigned_vehicles', null=True, blank=True, verbose_name=_("Serviser") ) # Označava je li vozilo u vlasništvu naše tvrtke (fleet) ili kupca is_company_vehicle = models.BooleanField( default=True, verbose_name=_("Vozilo tvrtke"), help_text=_("Označite ako vozilo pripada našoj tvrtki. Poništite za vozila kupaca.") ) # Kupac kojemu pripada vozilo (samo za vozila kupaca) client = models.ForeignKey( 'crm.Client', on_delete=models.SET_NULL, null=True, blank=True, related_name='vehicles', verbose_name=_("Kupac"), help_text=_("Popunite samo za vozila kupaca (is_company_vehicle = Ne).") ) is_active = models.BooleanField(default=True, verbose_name=_("Aktivno")) class Meta: verbose_name = _("Vozilo") verbose_name_plural = _("Vozila") ordering = ['registration_number'] def __str__(self): return f"{self.registration_number} - {self.make} {self.model}" def clean(self): super().clean() if self.asset_type == 'crane': if not self.crane_serial_number and self._state.adding: raise ValidationError({'crane_serial_number': _("Serijski broj dizalice je obavezan za dizalice.")}) if self.crane_serial_number and not re.fullmatch(r"[0-9]{6}", str(self.crane_serial_number or '')): raise ValidationError({'crane_serial_number': _("Serijski broj dizalice mora sadržavati točno 6 znamenki.")}) elif self.crane_serial_number: if not re.fullmatch(r"[0-9]{6}", str(self.crane_serial_number or '')): raise ValidationError({'crane_serial_number': _("Serijski broj dizalice mora sadržavati točno 6 znamenki.")}) def next_service_at(self) -> int: """ Vrati kilometražu pri kojoj sljedeći servis pada, bazirano na trenutnoj kilometraži i intervalu. Npr. current=12000, interval=15000 -> next_service_at = 15000 Npr. current=32000, interval=15000 -> next_service_at = 45000 """ if self.service_interval_km <= 0: return 0 blocks = (self.current_mileage // self.service_interval_km) + 1 return blocks * self.service_interval_km class Crane(Vehicle): class Meta: proxy = True verbose_name = _("Dizalica") verbose_name_plural = _("Dizalice") ordering = ['registration_number'] def __str__(self): details = " ".join(part for part in [self.make, self.model] if part) if details: return f"{self.registration_number} - {details}" return self.registration_number class WorkOrder(BaseModel): """ Putni nalog / radni nalog koji prati kilometražu vozila tijekom zadatka na terenu. Kad se postavi end_mileage, model ažurira vehicle.current_mileage ako je end_mileage veći. """ STATUS_CHOICES = [ ('open', 'Otvoren'), ('closed', 'Zatvoren'), ] PURPOSE_CHOICES = [ ('defektaza', _("Defektaža")), ('kontrola', _("Kontrola")), ('redovni_pregled', _("Redovni pregled")), ('popravak', _("Popravak")), ] vehicle = models.ForeignKey(Vehicle, on_delete=models.PROTECT, related_name='work_orders', verbose_name=_("Vozilo")) creator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='created_work_orders', verbose_name=_("Kreirao")) date = models.DateField(auto_now_add=True, verbose_name=_("Datum")) start_mileage = models.PositiveIntegerField(null=True, blank=True, verbose_name=_("Početna kilometraža")) end_mileage = models.PositiveIntegerField(null=True, blank=True, verbose_name=_("Završna kilometraža")) servicer_vehicle_registration = models.CharField( max_length=20, blank=True, verbose_name=_("Vozilo servisera - registracija"), ) servicer_vehicle_make_model = models.CharField( max_length=120, blank=True, verbose_name=_("Vozilo servisera - marka/model"), ) servicer_vehicle_start_mileage = models.PositiveIntegerField( null=True, blank=True, verbose_name=_("Vozilo servisera - početna kilometraža"), ) servicer_vehicle_end_mileage = models.PositiveIntegerField( null=True, blank=True, verbose_name=_("Vozilo servisera - završna kilometraža"), ) servicer_vehicle_fuel_cost = models.DecimalField( max_digits=10, decimal_places=2, null=True, blank=True, verbose_name=_("Vozilo servisera - trošak goriva (EUR)"), ) origin_location = models.CharField(max_length=255, blank=True, verbose_name=_("Lokacija ishodišta")) location = models.CharField(max_length=255, blank=True, verbose_name=_("Lokacija odredišta")) travel_start_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Početak puta")) travel_end_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Kraj puta")) display_code = models.CharField( max_length=8, blank=True, db_index=True, verbose_name=_("Prikaz putnog naloga"), help_text=_("Format: MT070626 (2 slova + 6 znamenki)."), ) purpose = models.CharField(max_length=50, choices=PURPOSE_CHOICES, blank=True, verbose_name=_("Svrha")) notes = models.TextField(blank=True, verbose_name=_("Bilješke")) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open', verbose_name=_("Status")) class Meta: verbose_name = _("Putni nalog") verbose_name_plural = _("Putni nalozi") ordering = ['-created_at'] def __str__(self): return f"WO-{self.pk} {self.vehicle.registration_number} ({self.status})" @property def distance(self): if self.end_mileage is None or self.start_mileage is None: return None return max(0, self.end_mileage - self.start_mileage) def save(self, *args, **kwargs): self.full_clean() # Provjera validnosti prije svega updating_vehicle = False if self.end_mileage is not None: # Ako je end_mileage veći od trenutne vehicle.current_mileage, ažuriraj vozilo. if self.end_mileage > (self.vehicle.current_mileage or 0): self.vehicle.current_mileage = self.end_mileage updating_vehicle = True super().save(*args, **kwargs) if updating_vehicle: # Spremi vozilo (nakon što nalog ima PK) self.vehicle.save() def clean(self): super().clean() if self.display_code: self.display_code = str(self.display_code).strip().upper() if not re.fullmatch(r"[A-Z]{2}[0-9]{6}", self.display_code): raise ValidationError({ "display_code": _("Prikaz putnog naloga mora biti u formatu MT070626 (2 slova + 6 znamenki).") }) if self.travel_start_at and self.travel_end_at and self.travel_end_at < self.travel_start_at: raise ValidationError({"travel_end_at": _("Kraj puta ne može biti prije početka puta.")}) class WorkOrderInvoice(BaseModel): work_order = models.ForeignKey( WorkOrder, on_delete=models.CASCADE, related_name='invoices', verbose_name=_("Putni nalog"), ) naziv_racuna = models.CharField(max_length=255, verbose_name=_("Naziv računa")) lokacija = models.CharField(max_length=255, blank=True, verbose_name=_("Lokacija")) datum = models.DateField(default=timezone.now, verbose_name=_("Datum")) opis = models.TextField(blank=True, verbose_name=_("Opis")) image = models.FileField( upload_to=_work_order_invoice_upload_to, null=True, blank=True, verbose_name=_("Datoteka računa"), ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='created_work_order_invoices', verbose_name=_("Kreirao"), ) is_active = models.BooleanField(default=True, verbose_name=_("Aktivan")) class Meta: verbose_name = _("Račun putnog naloga") verbose_name_plural = _("Računi putnog naloga") ordering = ['-datum', '-created_at'] def __str__(self): return f"{self.naziv_racuna} ({self.work_order_id})" class WorkOrderAdditionalCostsTable(BaseModel): work_order = models.OneToOneField( WorkOrder, on_delete=models.CASCADE, related_name='additional_costs_table', verbose_name=_("Putni nalog"), ) data = models.JSONField( null=True, blank=True, default=None, verbose_name=_("Podaci tablice dodatnih troškova"), ) total_for_payout = models.DecimalField( max_digits=12, decimal_places=2, default=Decimal('0.00'), verbose_name=_("Ukupno za isplatu"), ) class Meta: ordering = ['-updated_at'] verbose_name = _("Tablica dodatnih troškova putnog naloga") verbose_name_plural = _("Tablice dodatnih troškova putnog naloga") def __str__(self): return f"Additional costs table for work order {self.work_order_id}" class WorkOrderPhoto(BaseModel): work_order = models.ForeignKey( WorkOrder, on_delete=models.CASCADE, related_name='photos', verbose_name=_("Putni nalog"), ) image = models.ImageField( upload_to=_work_order_photo_upload_to, verbose_name=_("Fotografija putnog naloga"), ) description = models.CharField(max_length=255, blank=True, verbose_name=_("Opis")) uploaded_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='uploaded_work_order_photos', verbose_name=_("Postavio"), ) is_active = models.BooleanField(default=True, verbose_name=_("Aktivan")) class Meta: verbose_name = _("Fotografija putnog naloga") verbose_name_plural = _("Fotografije putnog naloga") ordering = ['-created_at'] def __str__(self): return f"WorkOrderPhoto {self.pk} ({self.work_order_id})" class GeneratedWorkOrderPdf(BaseModel): PDF_TYPE_CHOICES = [ ('work_order', _("Putni nalog")), ('service_records', _("Servisni zapisi")), ('invoices', _("Računi")), ] STATUS_CHOICES = [ ('pending', _("U obradi")), ('ready', _("Spremno")), ('failed', _("Neuspješno")), ] work_order = models.ForeignKey( WorkOrder, on_delete=models.CASCADE, related_name='generated_pdfs', verbose_name=_("Putni nalog"), ) requested_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='requested_generated_work_order_pdfs', verbose_name=_("Zatražio"), ) pdf_type = models.CharField(max_length=32, choices=PDF_TYPE_CHOICES, db_index=True, verbose_name=_("Tip PDF-a")) status = models.CharField(max_length=16, choices=STATUS_CHOICES, default='pending', db_index=True, verbose_name=_("Status")) file = models.FileField(upload_to=_generated_work_order_pdf_upload_to, null=True, blank=True, verbose_name=_("PDF datoteka")) filename = models.CharField(max_length=255, blank=True, verbose_name=_("Naziv datoteke")) expires_at = models.DateTimeField(null=True, blank=True, db_index=True, verbose_name=_("Vrijedi do")) error_message = models.TextField(blank=True, verbose_name=_("Poruka greške")) class Meta: verbose_name = _("Generirani PDF putnog naloga") verbose_name_plural = _("Generirani PDF-ovi putnih naloga") ordering = ['-created_at'] def __str__(self): return f"{self.pdf_type}:{self.work_order_id}:{self.status}" class VehicleServiceRecord(BaseModel): """ Zapisi o servisima/popravcima na vozilu. """ vehicle = models.ForeignKey(Vehicle, on_delete=models.PROTECT, related_name='service_records', verbose_name=_("Vozilo")) task = models.ForeignKey( 'task_management.Task', on_delete=models.SET_NULL, related_name='service_records', null=True, blank=True, verbose_name=_("Radni zadatak"), ) performed_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='performed_vehicle_services', verbose_name=_("Izvršio")) service_title = models.CharField(max_length=255, blank=True, default='', verbose_name=_("Naziv servisnog zapisa")) description = models.TextField(verbose_name=_("Opis posla")) parts = models.TextField(blank=True, verbose_name=_("Korišteni dijelovi")) cost = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'), verbose_name=_("Trošak")) service_date = models.DateField(auto_now_add=True, verbose_name=_("Datum servisa")) mileage = models.PositiveIntegerField(null=True, blank=True, verbose_name=_("Kilometraža pri servisu")) next_service_due_at = models.PositiveIntegerField(null=True, blank=True, verbose_name=_("Kilometraža sljedećeg servisa")) class Meta: verbose_name = _("Servisni zapis") verbose_name_plural = _("Servisni zapisi") ordering = ['-service_date'] def __str__(self): mileage_display = self.mileage if self.mileage is not None else "-" title = (self.service_title or '').strip() if title: return f"{title} ({self.vehicle.registration_number} @ {mileage_display})" return f"Service {self.vehicle.registration_number} @ {mileage_display}" def clean(self): super().clean() if self.task_id and self.vehicle_id: task = self.task if task.vehicle_id and task.vehicle_id != self.vehicle_id: raise ValidationError({'task': _("Radni zadatak mora biti povezan s istom dizalicom kao servisni zapis.")}) def save(self, *args, **kwargs): self.full_clean() # Provjera validnosti prije svega # Ako nije zadan next_service_due_at, postavi prema vehicle.service_interval_km if self.next_service_due_at is None and self.mileage is not None and self.vehicle and self.vehicle.service_interval_km: self.next_service_due_at = self.mileage + self.vehicle.service_interval_km # VAŽNO: ažuriraj vehicle.current_mileage PRIJE save-a kako bi refresh-om nakon toga korisnik vidio svježe podatke if self.mileage is not None and self.mileage > (self.vehicle.current_mileage or 0): self.vehicle.current_mileage = self.mileage self.vehicle.save(update_fields=['current_mileage']) super().save(*args, **kwargs) class VehicleNotification(BaseModel): """ Jednostavni zapis notifikacije vezane uz fleet objekte. Može se koristiti za in-app obavijesti ili kao log koji je poslan. """ LEVEL_CHOICES = [ ('info', 'Info'), ('warning', 'Upozorenje'), ('critical', 'Kritično'), ] recipient = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='vehicle_notifications', verbose_name=_("Primatelj") ) title = models.CharField(max_length=255, verbose_name=_("Naslov")) message = models.TextField(verbose_name=_("Poruka")) metadata = models.JSONField(default=dict, blank=True, verbose_name=_("Metapodaci")) level = models.CharField(max_length=20, choices=LEVEL_CHOICES, default='info', verbose_name=_("Razina")) is_sent = models.BooleanField(default=False, verbose_name=_("Poslano (e-mail)")) is_read = models.BooleanField(default=False, verbose_name=_("Pročitano")) class Meta: verbose_name = _("Notifikacija za vozilo") verbose_name_plural = _("Notifikacije za vozila") ordering = ['-created_at'] def __str__(self): return f"{self.title} -> {self.recipient} ({self.level})" class EmailDispatchLog(BaseModel): DISPATCH_TYPE_CHOICES = [ ('work_order', _("Putni nalog")), ('service_record', _("Servisni zapis")), ] STATUS_CHOICES = [ ('queued', _("U obradi")), ('sent', _("Poslano")), ('failed', _("Neuspješno")), ] dispatch_type = models.CharField(max_length=32, choices=DISPATCH_TYPE_CHOICES, db_index=True, verbose_name=_("Tip slanja")) requested_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='requested_email_dispatch_logs', verbose_name=_("Zatražio"), ) work_order = models.ForeignKey( 'fleet.WorkOrder', null=True, blank=True, on_delete=models.SET_NULL, related_name='email_dispatch_logs', verbose_name=_("Putni nalog"), ) service_record = models.ForeignKey( 'fleet.VehicleServiceRecord', null=True, blank=True, on_delete=models.SET_NULL, related_name='email_dispatch_logs', verbose_name=_("Servisni zapis"), ) recipients = models.JSONField(default=list, blank=True, verbose_name=_("Primatelji")) subject = models.CharField(max_length=255, blank=True, verbose_name=_("Naslov")) message = models.TextField(blank=True, verbose_name=_("Poruka")) attachments = models.JSONField(default=list, blank=True, verbose_name=_("Privitci")) status = models.CharField(max_length=16, choices=STATUS_CHOICES, default='queued', db_index=True, verbose_name=_("Status")) error_message = models.TextField(blank=True, verbose_name=_("Greška")) metadata = models.JSONField(default=dict, blank=True, verbose_name=_("Metapodaci")) sent_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Poslano u")) class Meta: verbose_name = _("Log slanja emaila") verbose_name_plural = _("Logovi slanja emaila") ordering = ['-created_at'] def __str__(self): return f"{self.dispatch_type} -> {len(self.recipients or [])} ({self.status})" def clean(self): super().clean() if self.dispatch_type == 'work_order' and not self.work_order_id: raise ValidationError({'work_order': _("Putni nalog je obavezan za ovaj tip slanja.")}) if self.dispatch_type == 'service_record' and not self.service_record_id: raise ValidationError({'service_record': _("Servisni zapis je obavezan za ovaj tip slanja.")}) class ServiceContextNote(BaseModel): AUDIENCE_SOURCE_CHOICES = [ ('self', 'Samo autor'), ('member', 'Odabrani član tima'), ('team', 'Svi članovi tima'), ] created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='created_service_context_notes', verbose_name=_("Kreirao"), ) recipient = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='service_context_notes', verbose_name=_("Primatelj"), ) audience_source = models.CharField( max_length=16, choices=AUDIENCE_SOURCE_CHOICES, default='self', verbose_name=_("Publika"), ) work_order = models.ForeignKey( WorkOrder, on_delete=models.SET_NULL, null=True, blank=True, related_name='service_context_notes', verbose_name=_("Putni nalog"), ) task = models.ForeignKey( 'task_management.Task', on_delete=models.SET_NULL, null=True, blank=True, related_name='service_context_notes', verbose_name=_("Radni zadatak"), ) note = models.TextField(verbose_name=_("Bilješka")) note_date = models.DateField( null=True, blank=True, db_index=True, verbose_name=_("Datum podsjetnika"), ) is_closed = models.BooleanField(default=False, verbose_name=_("Ručno zatvoreno")) closed_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Vrijeme zatvaranja")) class Meta: verbose_name = _("Bilješka servisnog konteksta") verbose_name_plural = _("Bilješke servisnog konteksta") ordering = ['is_closed', 'note_date', '-created_at'] def __str__(self): return f"Note {self.pk} -> {self.recipient_id}" def clean(self): super().clean() if self.task_id and self.work_order_id and self.task and self.task.work_order_id: if self.task.work_order_id != self.work_order_id: raise ValidationError({ 'task': _("Radni zadatak mora biti povezan s istim putnim nalogom kao bilješka."), }) class MonthlyServicerDayEntry(BaseModel): ENTRY_TYPE_CHOICES = [ ('office', _('Rad u uredu')), ('sick_leave', _('Bolovanje')), ('vacation', _('Godišnji odmor')), ] user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='monthly_servicer_day_entries', verbose_name=_('Serviser'), ) entry_date = models.DateField(db_index=True, verbose_name=_('Datum')) entry_type = models.CharField( max_length=24, choices=ENTRY_TYPE_CHOICES, verbose_name=_('Vrsta unosa'), ) description = models.CharField(max_length=255, blank=True, verbose_name=_('Opis posla')) location = models.CharField(max_length=255, blank=True, verbose_name=_('Mjesto rada')) start_time = models.TimeField(null=True, blank=True, verbose_name=_('Početak rada')) end_time = models.TimeField(null=True, blank=True, verbose_name=_('Kraj rada')) regular_hours = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal('8.00'), verbose_name=_('Redovan rad'), ) overtime_hours = models.DecimalField( max_digits=5, decimal_places=2, default=Decimal('0.00'), verbose_name=_('Prekovremeni rad'), ) class Meta: verbose_name = _('Dnevni unos mjesečnog izvještaja servisera') verbose_name_plural = _('Dnevni unosi mjesečnog izvještaja servisera') ordering = ['entry_date'] constraints = [ models.UniqueConstraint( fields=['user', 'entry_date', 'is_active'], name='uniq_monthly_servicer_day_entry_user_date_active', ), ] def __str__(self): return f"{self.user_id} @ {self.entry_date} ({self.entry_type})" def apply_defaults(self): if self.entry_type == 'office': self.description = 'Rad u uredu' self.location = 'Zagreb (ured)' self.start_time = time(8, 0) self.end_time = time(16, 0) self.regular_hours = Decimal('8.00') self.overtime_hours = Decimal('0.00') return if self.entry_type == 'vacation': self.description = 'Godišnji odmor' elif self.entry_type == 'sick_leave': self.description = 'Bolovanje' self.location = '' self.start_time = None self.end_time = None self.regular_hours = Decimal('8.00') self.overtime_hours = Decimal('0.00') def clean(self): super().clean() self.apply_defaults() def save(self, *args, **kwargs): self.full_clean() return super().save(*args, **kwargs)