135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
"""
|
|
Celery tasks для автоматизации кадровых событий из заявок.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from celery import shared_task
|
|
from django.utils import timezone
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@shared_task(bind=True, max_retries=3)
|
|
def create_scheduled_action(self, request_id):
|
|
"""
|
|
Создать кадровое событие для заявки по расписанию.
|
|
|
|
Вызывается в date_from для vacation/sick_leave/day_off.
|
|
|
|
Args:
|
|
request_id: ID заявки
|
|
"""
|
|
from .models import Request
|
|
from .enums import RequestStatus
|
|
from employees.constants import (
|
|
ACTION_ON_DAY_OFF,
|
|
ACTION_ON_LEAVE,
|
|
ACTION_ON_MATERNITY,
|
|
ACTION_ON_SICK_LEAVE,
|
|
)
|
|
from employees.services.request_actions import (
|
|
build_request_action_comment,
|
|
create_request_action,
|
|
)
|
|
|
|
try:
|
|
request = Request.objects.get(id=request_id)
|
|
|
|
# Проверяем, что заявка все еще одобрена
|
|
if request.status != RequestStatus.APPROVED:
|
|
logger.info(
|
|
f"Request #{request_id} is no longer approved "
|
|
f"(status={request.status}), skipping action creation"
|
|
)
|
|
return
|
|
|
|
# Определяем тип события
|
|
action_mapping = {
|
|
"vacation": ACTION_ON_LEAVE,
|
|
"sick_leave": ACTION_ON_SICK_LEAVE,
|
|
"day_off": ACTION_ON_DAY_OFF,
|
|
"maternity": ACTION_ON_MATERNITY,
|
|
}
|
|
|
|
action_type = action_mapping.get(request.type)
|
|
if not action_type:
|
|
logger.warning(
|
|
f"Request #{request_id} has type '{request.type}' "
|
|
f"which doesn't map to scheduled action"
|
|
)
|
|
return
|
|
|
|
action, created = create_request_action(
|
|
request=request,
|
|
action_type=action_type,
|
|
action_date=request.date_from,
|
|
date_to=request.date_to,
|
|
comment=build_request_action_comment(request),
|
|
extra={
|
|
"approved_by": (request.approver.id if request.approver else None),
|
|
"scheduled": True,
|
|
},
|
|
)
|
|
if not created:
|
|
logger.info(f"Action already exists for Request #{request_id}, skipping")
|
|
return
|
|
|
|
logger.info(
|
|
f"Created scheduled EmployeeAction #{action.id} ({action_type}) "
|
|
f"for Request #{request_id}"
|
|
)
|
|
|
|
return f"Created action {action.id}"
|
|
|
|
except Request.DoesNotExist:
|
|
logger.error(f"Request #{request_id} not found")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Failed to create scheduled action for Request #{request_id}: {e}",
|
|
exc_info=True,
|
|
)
|
|
# Retry через 5 минут
|
|
raise self.retry(exc=e, countdown=300)
|
|
|
|
|
|
@shared_task(bind=True, max_retries=3)
|
|
def schedule_auto_return(self, request_id):
|
|
"""
|
|
Legacy no-op для старых очередей Celery.
|
|
|
|
Возвраты больше не закрывают временные кадровые состояния: период
|
|
хранится в EmployeeAction.date_to. Старые запланированные задачи должны
|
|
спокойно завершаться и не создавать дублирующих событий.
|
|
"""
|
|
from .models import Request
|
|
|
|
try:
|
|
request = Request.objects.get(id=request_id)
|
|
logger.info(
|
|
"Skipping legacy auto-return for Request #%s; interval ends at %s",
|
|
request_id,
|
|
request.date_to,
|
|
)
|
|
return "Auto-return disabled"
|
|
|
|
except Request.DoesNotExist:
|
|
logger.error(f"Request #{request_id} not found for auto-return")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Failed to process legacy auto-return for Request #{request_id}: {e}",
|
|
exc_info=True,
|
|
)
|
|
raise self.retry(exc=e, countdown=300)
|
|
|
|
|
|
@shared_task
|
|
def cleanup_missed_returns():
|
|
"""
|
|
Legacy no-op: возвратные события больше не создаются автоматически.
|
|
"""
|
|
logger.info("cleanup_missed_returns skipped: auto-return is disabled")
|
|
return 0
|