116 lines
3.5 KiB
Python
116 lines
3.5 KiB
Python
"""Idempotent EmployeeAction creation for approved requests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime, time
|
|
from typing import Any
|
|
|
|
from django.db import IntegrityError, transaction
|
|
from django.utils import timezone
|
|
|
|
from employees.models import EmployeeAction
|
|
|
|
|
|
def as_action_datetime(value: date | datetime | None, *, hour: int = 12) -> datetime:
|
|
"""Normalize request dates to aware datetimes for EmployeeAction.date."""
|
|
if value is None:
|
|
return timezone.now()
|
|
if isinstance(value, datetime):
|
|
return value if timezone.is_aware(value) else timezone.make_aware(value)
|
|
return timezone.make_aware(datetime.combine(value, time(hour=hour)))
|
|
|
|
|
|
def build_request_action_comment(request, prefix: str = "Заявление") -> str:
|
|
comment = f"{prefix} #{request.id}"
|
|
if getattr(request, "comment", ""):
|
|
comment += f": {request.comment[:200]}"
|
|
return comment
|
|
|
|
|
|
def get_existing_request_action(request, action_type: str) -> EmployeeAction | None:
|
|
"""Find an action by the new FK, falling back to legacy extra.request_id."""
|
|
existing = (
|
|
EmployeeAction.objects.filter(source_request=request, action=action_type)
|
|
.order_by("id")
|
|
.first()
|
|
)
|
|
if existing:
|
|
return existing
|
|
|
|
return (
|
|
EmployeeAction.objects.filter(
|
|
source_request__isnull=True,
|
|
extra__request_id=request.id,
|
|
action=action_type,
|
|
)
|
|
.order_by("id")
|
|
.first()
|
|
)
|
|
|
|
|
|
def create_request_action(
|
|
*,
|
|
request,
|
|
action_type: str,
|
|
action_date: date | datetime | None,
|
|
date_to: date | None = None,
|
|
comment: str,
|
|
extra: dict[str, Any] | None = None,
|
|
) -> tuple[EmployeeAction, bool]:
|
|
"""
|
|
Create one EmployeeAction per request/action pair.
|
|
|
|
Returns:
|
|
tuple: (action, created)
|
|
"""
|
|
extra_data = dict(extra or {})
|
|
extra_data["request_id"] = request.id
|
|
|
|
legacy = get_existing_request_action(request, action_type)
|
|
if legacy:
|
|
updates: list[str] = []
|
|
if legacy.source_request_id is None:
|
|
legacy.source_request = request
|
|
updates.append("source_request")
|
|
if not isinstance(legacy.extra, dict) or legacy.extra.get("request_id") is None:
|
|
current_extra = legacy.extra if isinstance(legacy.extra, dict) else {}
|
|
legacy.extra = {**current_extra, "request_id": request.id}
|
|
updates.append("extra")
|
|
if date_to is not None and legacy.date_to is None:
|
|
legacy.date_to = date_to
|
|
updates.append("date_to")
|
|
if updates:
|
|
try:
|
|
legacy.save(update_fields=updates)
|
|
except IntegrityError:
|
|
existing = EmployeeAction.objects.get(
|
|
source_request=request,
|
|
action=action_type,
|
|
)
|
|
return existing, False
|
|
return legacy, False
|
|
|
|
defaults = {
|
|
"employee": request.employee,
|
|
"date": as_action_datetime(action_date),
|
|
"date_to": date_to,
|
|
"comment": comment,
|
|
"extra": extra_data,
|
|
}
|
|
|
|
try:
|
|
with transaction.atomic():
|
|
action, created = EmployeeAction.objects.get_or_create(
|
|
source_request=request,
|
|
action=action_type,
|
|
defaults=defaults,
|
|
)
|
|
except IntegrityError:
|
|
action = EmployeeAction.objects.get(
|
|
source_request=request,
|
|
action=action_type,
|
|
)
|
|
created = False
|
|
|
|
return action, created
|