feat: dodaj bilješke widget i admin kalendar/notifikacije
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

Uveden je novi Notes drawer s kalendar integracijom, zajednički DRY
left-slide mehanizam te međusobno isključivo prikazivanje s Kalendar
widgetom.

Backend i frontend su prošireni za servisne bilješke (uključujući slanje
na više servisera), dodan je users/servicers endpoint, te su uvedene
warning notifikacije kada serviser ima više od 2 zadatka isti dan.

U admin su dodane stavke za Bilješke i zaseban Kalendar zadataka (proxy
TaskCalendar) radi lakšeg operativnog pregleda.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
mariomitte
2026-07-20 06:33:36 +02:00
parent 61996bd1a0
commit 1a18498b30
20 changed files with 1312 additions and 113 deletions

View File

@@ -1,8 +1,9 @@
# backend/modules/task_management/services.py
from django.db import transaction
from django.db import transaction, models
from rest_framework.exceptions import ValidationError
from django.core.exceptions import ValidationError as DjangoValidationError
from django.contrib.auth import get_user_model
import logging
from .models import Task, TaskTemplate, TaskWorkEntry
@@ -10,6 +11,21 @@ from .exceptions import OpenWorkOrderBlockingTaskClose
from modules.fleet.models import VehicleServiceRecord
logger = logging.getLogger(__name__)
User = get_user_model()
def _task_overload_recipients(task):
recipients = []
if task.assigned_to_id:
recipients.append(task.assigned_to)
supervisors = User.objects.filter(is_active=True).filter(
models.Q(is_staff=True) | (models.Q(is_team_member=True) & models.Q(is_serviser=False))
)
recipients.extend(supervisors)
unique = {}
for recipient in recipients:
unique[str(recipient.id)] = recipient
return list(unique.values())
class TaskService:
@@ -53,6 +69,7 @@ class TaskService:
with transaction.atomic():
entry.save()
TaskService._notify_if_servicer_overloaded(entry)
if template:
TaskService._create_work_entries_from_template(task=entry, template=template)
TaskService._create_service_records_from_template(task=entry, template=template, user=user)
@@ -60,6 +77,49 @@ class TaskService:
logger.info(f"Zadatak uspješno kreiran: {entry.id}")
return entry
@staticmethod
def _notify_if_servicer_overloaded(entry: Task):
if not entry.assigned_to_id or not entry.scheduled_date:
return
if not getattr(entry.assigned_to, 'is_serviser', False):
return
task_count = Task.objects.filter(
is_active=True,
assigned_to=entry.assigned_to,
scheduled_date=entry.scheduled_date,
status__in=('aktivan', 'servis', 'spreman_za_zavrsetak'),
).count()
if task_count <= 2:
return
from modules.fleet.services import NotificationService
schedule_label = entry.scheduled_date.strftime('%d.%m.%Y')
title = f"Preopterećenje rasporeda: {entry.assigned_to.get_full_name() or entry.assigned_to.email}"
message = (
f"Serviseru {entry.assigned_to.get_full_name() or entry.assigned_to.email} "
f"je dodijeljeno {task_count} zadatka za {schedule_label}. "
f"Sustav je dopustio i dodatni zadatak."
)
metadata = {
"entity_type": "task_overload",
"assigned_to_id": str(entry.assigned_to_id),
"task_id": str(entry.id),
"scheduled_date": entry.scheduled_date.isoformat(),
"task_count": task_count,
"section": "dashboard",
}
for recipient in _task_overload_recipients(entry):
NotificationService.create_notification(
recipient=recipient,
title=title,
message=message,
level='warning',
send_email=False,
metadata=metadata,
)
@staticmethod
def _create_work_entries_from_template(*, task: Task, template: TaskTemplate):
template_entries = list(template.entries.filter(is_active=True).order_by('sort_order', 'created_at')[:10])