feat: dnevni unosi i svi dani u mjesecnom izvjestaju servisera
Some checks failed
ERP CI/CD Pipeline / test (push) Has been cancelled
ERP CI/CD Pipeline / Deploy (server git pull + compose) (push) Has been cancelled

- Dodan model MonthlyServicerDayEntry s automatskom popunom prema tipu:
  office (08:00-16:00, Zagreb ured), vacation i sick_leave (8h bez vremena)
- Migracija 0034_monthlyservicerdayentry za novu tablicu s UniqueConstraint
- Novi ViewSet + serializer + endpoint fleet/monthly-servicer-day-entries/
  (upsert: POST za novi dan ili update za isti dan ako vec postoji)
- Backend DOCX report (_build_monthly_servicer_report_rows) sad generira
  redak za SVAKI dan u mjesecu: taskovi > rucni unos > prazan red
- TaskCalendarWidget potpuno prepisan bez mojibake znakova
  (zamjena UTF-8 specijalnih znakova ASCII ekvivalentima u string literalima)
- Tablica izvjestaja servisera: klikom na red bez naloga otvara se overlay
  s tri opcije (Rad u uredu / Bolovanje / Godisnji)
- TaskCalendarPortal: dodan calendarWorkOrders computed (svi statusi)
  kako bi report imao potpune podatke o radnom vremenu
- fetchMonthlyServicerEntries i upsertMonthlyServicerEntry u store-u

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
mariomitte
2026-07-31 16:42:07 +02:00
parent fe739b14c0
commit 3a33751012
8 changed files with 781 additions and 205 deletions

View File

@@ -1,5 +1,6 @@
import uuid
import re
from datetime import time
from django.db import models
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -694,4 +695,84 @@ class ServiceContextNote(BaseModel):
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)