Fix birthday event sync lifecycle
This commit is contained in:
parent
c5c4e1f725
commit
92fb845b70
@ -80,6 +80,9 @@ class Command(BaseCommand):
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f" ✓ Обновлено событий: {result['updated']}")
|
||||
)
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f" ✓ Удалено устаревших событий: {result['deleted']}")
|
||||
)
|
||||
|
||||
if result["skipped"] > 0:
|
||||
self.stdout.write(
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
Использует паттерн Service Layer через django-service-objects.
|
||||
"""
|
||||
|
||||
import calendar as calendar_lib
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
@ -14,6 +15,14 @@ from schedule.models import Calendar, Event, Rule
|
||||
|
||||
# Константы
|
||||
BIRTHDAY_COLOR = "#FFC107" # Жёлтый/золотой цвет для дней рождения
|
||||
BIRTHDAY_TITLE_PREFIX = "🎂 День рождения:"
|
||||
|
||||
|
||||
def birthday_event_queryset(employee: Employee):
|
||||
"""События дня рождения, связанные с сотрудником."""
|
||||
return Event.objects.filter(
|
||||
creator_id=employee.pk, title__startswith=BIRTHDAY_TITLE_PREFIX
|
||||
)
|
||||
|
||||
|
||||
class UpsertBirthdayEventService(Service):
|
||||
@ -29,9 +38,15 @@ class UpsertBirthdayEventService(Service):
|
||||
def process(self):
|
||||
employee = self.cleaned_data["employee"]
|
||||
|
||||
# Проверяем наличие даты рождения
|
||||
if not employee.birth_date:
|
||||
return {"success": False, "reason": "no_birth_date", "event": None}
|
||||
should_have_event, reason = self._should_have_birthday_event(employee)
|
||||
if not should_have_event:
|
||||
deleted_count, _ = birthday_event_queryset(employee).delete()
|
||||
return {
|
||||
"success": False,
|
||||
"reason": reason,
|
||||
"event": None,
|
||||
"deleted_count": deleted_count,
|
||||
}
|
||||
|
||||
# Получаем общий календарь дней рождений
|
||||
birthday_calendar = self._get_or_create_birthday_calendar()
|
||||
@ -43,8 +58,22 @@ class UpsertBirthdayEventService(Service):
|
||||
|
||||
return {"success": True, "event": event, "created": created}
|
||||
|
||||
def _should_have_birthday_event(self, employee: Employee) -> tuple[bool, str | None]:
|
||||
"""Проверить, должен ли сотрудник попадать в календарь ДР."""
|
||||
if not employee.birth_date:
|
||||
return False, "no_birth_date"
|
||||
if not employee.email_verified:
|
||||
return False, "email_not_verified"
|
||||
if not employee.is_active:
|
||||
return False, "not_active"
|
||||
if not employee.is_actually_active:
|
||||
return False, "not_actually_active"
|
||||
return True, None
|
||||
|
||||
def _get_or_create_birthday_calendar(self) -> Calendar:
|
||||
"""Получить или создать общий календарь дней рождений."""
|
||||
# TODO: отдельно настроить доступность календаря birthdays через
|
||||
# CalendarRelation/CalendarBinding для обычных пользователей.
|
||||
calendar, created = Calendar.objects.get_or_create(
|
||||
slug="birthdays", defaults={"name": "🎂 Дни рождения"}
|
||||
)
|
||||
@ -58,29 +87,20 @@ class UpsertBirthdayEventService(Service):
|
||||
|
||||
Возвращает (event, created).
|
||||
"""
|
||||
title = f"🎂 День рождения: {str(employee)}"
|
||||
title = f"{BIRTHDAY_TITLE_PREFIX} {str(employee)}"
|
||||
|
||||
# Ищем существующее событие по creator + начало title
|
||||
# (паттерн External ID).
|
||||
# НЕ фильтруем по календарю, чтобы найти старые события в персональных
|
||||
# календарях
|
||||
existing_event = Event.objects.filter(
|
||||
creator_id=employee.pk, title__startswith="🎂 День рождения:"
|
||||
).first()
|
||||
existing_event = birthday_event_queryset(employee).first()
|
||||
|
||||
# Создаем правило для ежегодного повторения
|
||||
rule = self._get_or_create_yearly_rule()
|
||||
|
||||
# Вычисляем даты для текущего года
|
||||
current_year = timezone.now().year
|
||||
start_date = datetime(
|
||||
year=current_year,
|
||||
month=employee.birth_date.month,
|
||||
day=employee.birth_date.day,
|
||||
hour=0,
|
||||
minute=0,
|
||||
second=0,
|
||||
)
|
||||
start_date = self._birthday_start_for_year(employee, current_year)
|
||||
|
||||
# Делаем aware ПЕРЕД вычислением end_date
|
||||
if timezone.is_naive(start_date):
|
||||
@ -103,9 +123,7 @@ class UpsertBirthdayEventService(Service):
|
||||
existing_event.save()
|
||||
|
||||
# Удаляем дубликаты (другие события ДР этого сотрудника)
|
||||
Event.objects.filter(
|
||||
creator_id=employee.pk, title__startswith="🎂 День рождения:"
|
||||
).exclude(pk=existing_event.pk).delete()
|
||||
birthday_event_queryset(employee).exclude(pk=existing_event.pk).delete()
|
||||
|
||||
return existing_event, False # Обновлено
|
||||
else:
|
||||
@ -125,6 +143,23 @@ class UpsertBirthdayEventService(Service):
|
||||
)
|
||||
return event, True # Создано
|
||||
|
||||
def _birthday_start_for_year(self, employee: Employee, year: int) -> datetime:
|
||||
"""Дата события в конкретном году с fallback для 29 февраля."""
|
||||
birth_date = employee.birth_date
|
||||
month = birth_date.month
|
||||
day = birth_date.day
|
||||
if month == 2 and day == 29 and not calendar_lib.isleap(year):
|
||||
day = 28
|
||||
|
||||
return datetime(
|
||||
year=year,
|
||||
month=month,
|
||||
day=day,
|
||||
hour=0,
|
||||
minute=0,
|
||||
second=0,
|
||||
)
|
||||
|
||||
def _get_or_create_yearly_rule(self) -> Rule:
|
||||
"""Получить или создать правило для ежегодного повторения."""
|
||||
rule, created = Rule.objects.get_or_create(
|
||||
@ -151,9 +186,7 @@ class DeleteBirthdayEventService(Service):
|
||||
employee = self.cleaned_data["employee"]
|
||||
|
||||
# Ищем событие по creator + паттерн title
|
||||
deleted_count, _ = Event.objects.filter(
|
||||
creator_id=employee.pk, title__startswith="🎂 День рождения:"
|
||||
).delete()
|
||||
deleted_count, _ = birthday_event_queryset(employee).delete()
|
||||
|
||||
return {"success": deleted_count > 0, "deleted_count": deleted_count}
|
||||
|
||||
@ -166,14 +199,13 @@ class BulkSyncBirthdaysService(Service):
|
||||
"""
|
||||
|
||||
def process(self):
|
||||
employees = Employee.objects.filter(
|
||||
birth_date__isnull=False
|
||||
).select_related()
|
||||
employees = Employee.objects.all().select_related()
|
||||
|
||||
results = {
|
||||
"total": 0,
|
||||
"created": 0,
|
||||
"updated": 0,
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"errors": [],
|
||||
}
|
||||
@ -190,6 +222,8 @@ class BulkSyncBirthdaysService(Service):
|
||||
results["created"] += 1
|
||||
else:
|
||||
results["updated"] += 1
|
||||
elif result.get("deleted_count", 0):
|
||||
results["deleted"] += result["deleted_count"]
|
||||
else:
|
||||
results["skipped"] += 1
|
||||
except Exception as e:
|
||||
|
||||
@ -11,9 +11,11 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from django.db.models.signals import post_delete, post_save
|
||||
from django.db.models.signals import post_delete, post_save, pre_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from employees.constants import ACTIVATING_MARKER_ACTIONS, PERMANENT_ACTIONS
|
||||
from employees.models import Employee
|
||||
from employees.services import (
|
||||
UpsertBirthdayEventService,
|
||||
DeleteBirthdayEventService,
|
||||
@ -23,6 +25,19 @@ from employees.services import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _action_affects_birthday_sync(action: str | None) -> bool:
|
||||
return action in PERMANENT_ACTIONS or action in ACTIVATING_MARKER_ACTIONS
|
||||
|
||||
|
||||
def _sync_birthday_for_employee_id(employee_id: int | None) -> None:
|
||||
if not employee_id:
|
||||
return
|
||||
employee = Employee.objects.filter(pk=employee_id).first()
|
||||
if not employee:
|
||||
return
|
||||
UpsertBirthdayEventService.execute({"employee": employee})
|
||||
|
||||
|
||||
@receiver(post_save)
|
||||
def sync_birthday_event_on_employee_save(sender, instance, created, **kwargs):
|
||||
"""
|
||||
@ -52,6 +67,53 @@ def sync_birthday_event_on_employee_save(sender, instance, created, **kwargs):
|
||||
)
|
||||
|
||||
|
||||
@receiver(pre_save)
|
||||
def remember_employee_action_for_birthday_sync(sender, instance, **kwargs):
|
||||
"""Запоминает прежнее кадровое действие перед изменением."""
|
||||
if sender._meta.label != "employees.EmployeeAction":
|
||||
return
|
||||
if not instance.pk:
|
||||
return
|
||||
|
||||
previous = (
|
||||
sender.objects.filter(pk=instance.pk)
|
||||
.values("action", "employee_id")
|
||||
.first()
|
||||
)
|
||||
if not previous:
|
||||
return
|
||||
|
||||
instance._birthday_previous_action = previous["action"]
|
||||
instance._birthday_previous_employee_id = previous["employee_id"]
|
||||
|
||||
|
||||
@receiver(post_save)
|
||||
def sync_birthday_event_on_employee_action_save(sender, instance, **kwargs):
|
||||
"""
|
||||
Синхронизирует ДР при кадровых действиях, меняющих актуальность сотрудника.
|
||||
"""
|
||||
if sender._meta.label != "employees.EmployeeAction":
|
||||
return
|
||||
|
||||
employee_ids = set()
|
||||
if _action_affects_birthday_sync(instance.action):
|
||||
employee_ids.add(instance.employee_id)
|
||||
|
||||
previous_action = getattr(instance, "_birthday_previous_action", None)
|
||||
if _action_affects_birthday_sync(previous_action):
|
||||
employee_ids.add(getattr(instance, "_birthday_previous_employee_id", None))
|
||||
|
||||
for employee_id in employee_ids:
|
||||
try:
|
||||
_sync_birthday_for_employee_id(employee_id)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при синхронизации дня рождения после кадрового "
|
||||
f"события #{instance.pk}: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_delete)
|
||||
def delete_birthday_event_on_employee_delete(sender, instance, **kwargs):
|
||||
"""
|
||||
@ -76,3 +138,21 @@ def delete_birthday_event_on_employee_delete(sender, instance, **kwargs):
|
||||
f"Ошибка при удалении дня рождения для {instance}: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_delete)
|
||||
def sync_birthday_event_on_employee_action_delete(sender, instance, **kwargs):
|
||||
"""Пересинхронизирует ДР после удаления статусного кадрового действия."""
|
||||
if sender._meta.label != "employees.EmployeeAction":
|
||||
return
|
||||
if not _action_affects_birthday_sync(instance.action):
|
||||
return
|
||||
|
||||
try:
|
||||
_sync_birthday_for_employee_id(instance.employee_id)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при синхронизации дня рождения после удаления кадрового "
|
||||
f"события #{instance.pk}: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
337
backend/tests/employees/test_birthday_events.py
Normal file
337
backend/tests/employees/test_birthday_events.py
Normal file
@ -0,0 +1,337 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
from io import StringIO
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from django.core.management import call_command
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from schedule.models import Calendar, Event
|
||||
|
||||
from employees.constants import ACTION_DISMISSED, ACTION_REHIRED
|
||||
from employees.models import EmployeeAction
|
||||
from employees.services import BulkSyncBirthdaysService, UpsertBirthdayEventService
|
||||
from employees.services.birthday_events import (
|
||||
BIRTHDAY_COLOR,
|
||||
BIRTHDAY_TITLE_PREFIX,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def _birthday_events(employee):
|
||||
return Event.objects.filter(
|
||||
creator_id=employee.pk,
|
||||
title__startswith=BIRTHDAY_TITLE_PREFIX,
|
||||
)
|
||||
|
||||
|
||||
def _make_stale_birthday_event(employee):
|
||||
calendar, _ = Calendar.objects.get_or_create(
|
||||
slug="birthdays",
|
||||
defaults={"name": "🎂 Дни рождения"},
|
||||
)
|
||||
start = timezone.now()
|
||||
return Event.objects.create(
|
||||
title=f"{BIRTHDAY_TITLE_PREFIX} {employee}",
|
||||
start=start,
|
||||
end=start + timedelta(hours=1),
|
||||
calendar=calendar,
|
||||
creator=employee,
|
||||
color_event=BIRTHDAY_COLOR,
|
||||
)
|
||||
|
||||
|
||||
def _run_sync_birthdays(**options):
|
||||
output = StringIO()
|
||||
call_command("sync_birthdays", stdout=output, **options)
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
def _aware_datetime(year: int, month: int, day: int):
|
||||
return timezone.make_aware(datetime(year, month, day, 12))
|
||||
|
||||
|
||||
def _local_start(event):
|
||||
return timezone.localtime(event.start)
|
||||
|
||||
|
||||
def _local_end(event):
|
||||
return timezone.localtime(event.end)
|
||||
|
||||
|
||||
def test_birthday_event_created_for_actual_employee(user_factory):
|
||||
employee = user_factory(
|
||||
first_name="Иван",
|
||||
last_name="Петров",
|
||||
birth_date=date(1990, 5, 6),
|
||||
)
|
||||
|
||||
event = _birthday_events(employee).get()
|
||||
|
||||
assert event.calendar.slug == "birthdays"
|
||||
assert event.title == f"{BIRTHDAY_TITLE_PREFIX} Петров Иван"
|
||||
assert _local_start(event).month == 5
|
||||
assert _local_start(event).day == 6
|
||||
assert _local_end(event).hour == 23
|
||||
assert _local_end(event).minute == 59
|
||||
assert event.rule.frequency == "YEARLY"
|
||||
assert event.end_recurring_period is None
|
||||
assert event.color_event == BIRTHDAY_COLOR
|
||||
|
||||
|
||||
def test_birthday_event_moves_when_birth_date_changes(user_factory):
|
||||
employee = user_factory(birth_date=date(1990, 5, 6))
|
||||
original_event_id = _birthday_events(employee).get().id
|
||||
|
||||
employee.birth_date = date(1990, 7, 8)
|
||||
employee.save(update_fields=["birth_date"])
|
||||
|
||||
event = _birthday_events(employee).get()
|
||||
assert event.id == original_event_id
|
||||
assert _local_start(event).month == 7
|
||||
assert _local_start(event).day == 8
|
||||
|
||||
|
||||
def test_birthday_event_deleted_when_birth_date_is_cleared(user_factory):
|
||||
employee = user_factory(birth_date=date(1990, 5, 6))
|
||||
assert _birthday_events(employee).exists()
|
||||
|
||||
employee.birth_date = None
|
||||
employee.save(update_fields=["birth_date"])
|
||||
|
||||
assert not _birthday_events(employee).exists()
|
||||
|
||||
|
||||
def test_birthday_event_created_only_after_employee_becomes_actual(user_factory):
|
||||
employee = user_factory(
|
||||
verified=False,
|
||||
active=False,
|
||||
birth_date=date(1990, 5, 6),
|
||||
)
|
||||
assert not _birthday_events(employee).exists()
|
||||
|
||||
employee.email_verified = True
|
||||
employee.is_active = True
|
||||
employee.save(update_fields=["email_verified", "is_active"])
|
||||
|
||||
assert _birthday_events(employee).count() == 1
|
||||
|
||||
|
||||
def test_birthday_event_deleted_when_employee_is_dismissed(user_factory):
|
||||
employee = user_factory(birth_date=date(1990, 5, 6))
|
||||
assert _birthday_events(employee).exists()
|
||||
|
||||
EmployeeAction.objects.create(
|
||||
employee=employee,
|
||||
action=ACTION_DISMISSED,
|
||||
date=timezone.now(),
|
||||
)
|
||||
|
||||
assert not _birthday_events(employee).exists()
|
||||
|
||||
|
||||
def test_birthday_event_recreated_when_employee_is_rehired(user_factory):
|
||||
employee = user_factory(birth_date=date(1990, 5, 6))
|
||||
now = timezone.now()
|
||||
EmployeeAction.objects.create(
|
||||
employee=employee,
|
||||
action=ACTION_DISMISSED,
|
||||
date=now,
|
||||
)
|
||||
assert not _birthday_events(employee).exists()
|
||||
|
||||
EmployeeAction.objects.create(
|
||||
employee=employee,
|
||||
action=ACTION_REHIRED,
|
||||
date=now + timedelta(seconds=1),
|
||||
)
|
||||
|
||||
assert _birthday_events(employee).count() == 1
|
||||
|
||||
|
||||
def test_leap_day_birthday_uses_february_28_in_non_leap_year(user_factory):
|
||||
employee = user_factory(birth_date=date(1988, 2, 29))
|
||||
|
||||
with patch(
|
||||
"employees.services.birthday_events.timezone.now",
|
||||
return_value=_aware_datetime(2026, 1, 1),
|
||||
):
|
||||
UpsertBirthdayEventService.execute({"employee": employee})
|
||||
|
||||
event = _birthday_events(employee).get()
|
||||
assert _local_start(event).month == 2
|
||||
assert _local_start(event).day == 28
|
||||
|
||||
|
||||
def test_leap_day_birthday_uses_february_29_in_leap_year(user_factory):
|
||||
employee = user_factory(birth_date=date(1988, 2, 29))
|
||||
|
||||
with patch(
|
||||
"employees.services.birthday_events.timezone.now",
|
||||
return_value=_aware_datetime(2028, 1, 1),
|
||||
):
|
||||
UpsertBirthdayEventService.execute({"employee": employee})
|
||||
|
||||
event = _birthday_events(employee).get()
|
||||
assert _local_start(event).month == 2
|
||||
assert _local_start(event).day == 29
|
||||
|
||||
|
||||
def test_bulk_sync_deletes_stale_events_and_skips_inactual_employees(
|
||||
user_factory,
|
||||
):
|
||||
active = user_factory(birth_date=date(1990, 5, 6))
|
||||
without_birth_date = user_factory(birth_date=None)
|
||||
dismissed = user_factory(birth_date=date(1991, 7, 8))
|
||||
EmployeeAction.objects.create(
|
||||
employee=dismissed,
|
||||
action=ACTION_DISMISSED,
|
||||
date=timezone.now(),
|
||||
)
|
||||
|
||||
_make_stale_birthday_event(without_birth_date)
|
||||
_make_stale_birthday_event(dismissed)
|
||||
|
||||
result = BulkSyncBirthdaysService.execute({})
|
||||
|
||||
assert _birthday_events(active).count() == 1
|
||||
assert not _birthday_events(without_birth_date).exists()
|
||||
assert not _birthday_events(dismissed).exists()
|
||||
assert result["deleted"] >= 2
|
||||
|
||||
|
||||
def test_sync_birthdays_command_removes_dismissed_employee_stale_event(
|
||||
user_factory,
|
||||
):
|
||||
employee = user_factory(birth_date=date(1990, 5, 6))
|
||||
EmployeeAction.objects.create(
|
||||
employee=employee,
|
||||
action=ACTION_DISMISSED,
|
||||
date=timezone.now(),
|
||||
)
|
||||
stale_event = _make_stale_birthday_event(employee)
|
||||
assert _birthday_events(employee).filter(pk=stale_event.pk).exists()
|
||||
|
||||
output = _run_sync_birthdays()
|
||||
|
||||
assert not _birthday_events(employee).exists()
|
||||
assert "Обработано сотрудников: 1" in output
|
||||
assert "Удалено устаревших событий: 1" in output
|
||||
assert "Синхронизация завершена успешно" in output
|
||||
|
||||
|
||||
def test_sync_birthdays_command_dry_run_keeps_dismissed_stale_event(
|
||||
user_factory,
|
||||
):
|
||||
employee = user_factory(birth_date=date(1990, 5, 6))
|
||||
EmployeeAction.objects.create(
|
||||
employee=employee,
|
||||
action=ACTION_DISMISSED,
|
||||
date=timezone.now(),
|
||||
)
|
||||
stale_event = _make_stale_birthday_event(employee)
|
||||
|
||||
output = _run_sync_birthdays(dry_run=True)
|
||||
|
||||
assert _birthday_events(employee).filter(pk=stale_event.pk).exists()
|
||||
assert "DRY-RUN режим" in output
|
||||
assert "Удалено устаревших событий: 1" in output
|
||||
assert "Изменения НЕ применены" in output
|
||||
|
||||
|
||||
def test_sync_birthdays_command_handles_mixed_employee_states(user_factory):
|
||||
active_missing = user_factory(birth_date=date(1990, 5, 6))
|
||||
_birthday_events(active_missing).delete()
|
||||
|
||||
active_outdated = user_factory(birth_date=date(1991, 7, 8))
|
||||
outdated_event = _birthday_events(active_outdated).get()
|
||||
outdated_event.start = timezone.make_aware(datetime(2026, 1, 1, 12))
|
||||
outdated_event.end = timezone.make_aware(datetime(2026, 1, 1, 13))
|
||||
outdated_event.color_event = "#000000"
|
||||
outdated_event.save()
|
||||
duplicate_event = _make_stale_birthday_event(active_outdated)
|
||||
|
||||
without_birth_date = user_factory(birth_date=None)
|
||||
_make_stale_birthday_event(without_birth_date)
|
||||
|
||||
inactive = user_factory(active=False, birth_date=date(1992, 8, 9))
|
||||
_make_stale_birthday_event(inactive)
|
||||
|
||||
unverified = user_factory(verified=False, birth_date=date(1993, 9, 10))
|
||||
_make_stale_birthday_event(unverified)
|
||||
|
||||
dismissed = user_factory(birth_date=date(1994, 10, 11))
|
||||
EmployeeAction.objects.create(
|
||||
employee=dismissed,
|
||||
action=ACTION_DISMISSED,
|
||||
date=timezone.now(),
|
||||
)
|
||||
_make_stale_birthday_event(dismissed)
|
||||
|
||||
output = _run_sync_birthdays()
|
||||
|
||||
assert _birthday_events(active_missing).count() == 1
|
||||
|
||||
updated_event = _birthday_events(active_outdated).get()
|
||||
assert updated_event.pk == outdated_event.pk
|
||||
assert not Event.objects.filter(pk=duplicate_event.pk).exists()
|
||||
assert _local_start(updated_event).month == 7
|
||||
assert _local_start(updated_event).day == 8
|
||||
assert updated_event.color_event == BIRTHDAY_COLOR
|
||||
|
||||
assert not _birthday_events(without_birth_date).exists()
|
||||
assert not _birthday_events(inactive).exists()
|
||||
assert not _birthday_events(unverified).exists()
|
||||
assert not _birthday_events(dismissed).exists()
|
||||
|
||||
assert "Обработано сотрудников: 6" in output
|
||||
assert "Создано событий: 1" in output
|
||||
assert "Обновлено событий: 1" in output
|
||||
assert "Удалено устаревших событий: 4" in output
|
||||
|
||||
|
||||
def test_profile_patch_moves_birthday_event(api_client, user_factory):
|
||||
employee = user_factory(birth_date=date(1990, 5, 6))
|
||||
api_client.force_authenticate(user=employee)
|
||||
|
||||
response = api_client.patch(
|
||||
reverse("api:v1:employees-me"),
|
||||
{
|
||||
"first_name": employee.first_name,
|
||||
"last_name": employee.last_name,
|
||||
"patronymic": employee.patronymic,
|
||||
"birth_date": "1990-09-10",
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
event = _birthday_events(employee).get()
|
||||
assert _local_start(event).month == 9
|
||||
assert _local_start(event).day == 10
|
||||
|
||||
|
||||
def test_profile_patch_null_birth_date_deletes_birthday_event(
|
||||
api_client,
|
||||
user_factory,
|
||||
):
|
||||
employee = user_factory(birth_date=date(1990, 5, 6))
|
||||
assert _birthday_events(employee).exists()
|
||||
api_client.force_authenticate(user=employee)
|
||||
|
||||
response = api_client.patch(
|
||||
reverse("api:v1:employees-me"),
|
||||
{
|
||||
"first_name": employee.first_name,
|
||||
"last_name": employee.last_name,
|
||||
"patronymic": employee.patronymic,
|
||||
"birth_date": None,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert not _birthday_events(employee).exists()
|
||||
Loading…
x
Reference in New Issue
Block a user